diff options
| author | schneefux <schneefux+commit@schneefux.xyz> | 2014-06-10 15:24:45 +0200 |
|---|---|---|
| committer | schneefux <schneefux+commit@schneefux.xyz> | 2016-08-15 20:02:41 +0200 |
| commit | 83b02062f91d616a01a5daa035fbca7d097adc05 (patch) | |
| tree | 63008394cba75f43404066d25a63dd392585a227 | |
| parent | 46cf469a0dfa9088aaf059b2d051ecf9951beea7 (diff) | |
| download | wvs-vplan-83b02062f91d616a01a5daa035fbca7d097adc05.tar.gz wvs-vplan-83b02062f91d616a01a5daa035fbca7d097adc05.zip | |
aktualisiere PDF.js
| -rw-r--r-- | index.html | 1 | ||||
| -rw-r--r-- | js/index.js | 8 | ||||
| -rw-r--r-- | js/pdf.js | 3914 | ||||
| -rw-r--r-- | js/pdf.worker.js | 13355 |
4 files changed, 8993 insertions, 8285 deletions
@@ -129,6 +129,7 @@ <script type="text/javascript" src="js/jqm-themehack.js"></script> <script type="text/javascript" src="js/jquery.mobile-1.4.2.min.js"></script> <script type="text/javascript" src="cordova.js"></script> + <script type="text/javascript" src="js/compatibility.js"></script> <!-- sonst gibt es Probleme mit PDF.js, weil Android zu schlecht ist --> <script type="text/javascript" src="js/pdf.js"></script> <script type="text/javascript" src="js/moment-with-langs.min.js"></script> <script type="text/javascript" src="js/piwik.js"></script> diff --git a/js/index.js b/js/index.js index a22e96b..ddedc8f 100644 --- a/js/index.js +++ b/js/index.js @@ -598,12 +598,12 @@ function renderPage($div, pdf, pageNumber, callback){ var pageText = ""; var lastBlock = null; - for(j = 0; j < textContent.length; j++){ - var block = textContent[j]; + for(j = 0; j < textContent.items.length; j++){ + var block = textContent.items[j]; if(lastBlock !== null && lastBlock.str[lastBlock.str.length - 1] !== ' '){ - if(block.x < lastBlock.x){ + if(block.transform[4] < lastBlock.transform[4]){ pageText += '\n'; - }else if(lastBlock.y !== block.y && (lastBlock.str.match(/^(\s?[a-zA-Z])$|^(.+\s[a-zA-Z])$/) === null )){ + }else if(lastBlock.transform[5] !== block.transform[5] && (lastBlock.str.match(/^(\s?[a-zA-Z])$|^(.+\s[a-zA-Z])$/) === null )){ pageText += '\n'; } } @@ -21,8 +21,8 @@ if (typeof PDFJS === 'undefined') { (typeof window !== 'undefined' ? window : this).PDFJS = {}; } -PDFJS.version = '0.8.1239'; -PDFJS.build = '1a6e103'; +PDFJS.version = '1.0.276'; +PDFJS.build = 'a09aecb'; (function pdfjsWrapper() { // Use strict in our context only - users might not want it @@ -181,7 +181,9 @@ var OPS = PDFJS.OPS = { paintInlineImageXObject: 86, paintInlineImageXObjectGroup: 87, paintImageXObjectRepeat: 88, - paintImageMaskXObjectRepeat: 89 + paintImageMaskXObjectRepeat: 89, + paintSolidColorImageMask: 90, + constructPath: 91 }; // A notice for devs. These are good for things that are helpful to devs, such @@ -266,9 +268,10 @@ function combineUrl(baseUrl, url) { if (/^[a-z][a-z0-9+\-.]*:/i.test(url)) { return url; } + var i; if (url.charAt(0) == '/') { // absolute path - var i = baseUrl.indexOf('://'); + i = baseUrl.indexOf('://'); if (url.charAt(1) === '/') { ++i; } else { @@ -277,7 +280,7 @@ function combineUrl(baseUrl, url) { return baseUrl.substring(0, i) + url; } else { // relative path - var pathLength = baseUrl.length, i; + var pathLength = baseUrl.length; i = baseUrl.lastIndexOf('#'); pathLength = i >= 0 ? i : pathLength; i = baseUrl.lastIndexOf('?', pathLength); @@ -311,14 +314,6 @@ function isValidUrl(url, allowRelative) { } PDFJS.isValidUrl = isValidUrl; -// In a well-formed PDF, |cond| holds. If it doesn't, subsequent -// behavior is undefined. -function assertWellFormed(cond, msg) { - if (!cond) { - error(msg); - } -} - function shadow(obj, prop, value) { Object.defineProperty(obj, prop, { value: value, enumerable: true, @@ -422,23 +417,137 @@ var XRefParseException = (function XRefParseExceptionClosure() { function bytesToString(bytes) { - var strBuf = []; var length = bytes.length; - for (var n = 0; n < length; ++n) { - strBuf.push(String.fromCharCode(bytes[n])); + var MAX_ARGUMENT_COUNT = 8192; + if (length < MAX_ARGUMENT_COUNT) { + return String.fromCharCode.apply(null, bytes); + } + var strBuf = []; + for (var i = 0; i < length; i += MAX_ARGUMENT_COUNT) { + var chunkEnd = Math.min(i + MAX_ARGUMENT_COUNT, length); + var chunk = bytes.subarray(i, chunkEnd); + strBuf.push(String.fromCharCode.apply(null, chunk)); } return strBuf.join(''); } +function stringToArray(str) { + var length = str.length; + var array = []; + for (var i = 0; i < length; ++i) { + array[i] = str.charCodeAt(i); + } + return array; +} + function stringToBytes(str) { var length = str.length; var bytes = new Uint8Array(length); - for (var n = 0; n < length; ++n) { - bytes[n] = str.charCodeAt(n) & 0xFF; + for (var i = 0; i < length; ++i) { + bytes[i] = str.charCodeAt(i) & 0xFF; } return bytes; } +function string32(value) { + return String.fromCharCode((value >> 24) & 0xff, (value >> 16) & 0xff, + (value >> 8) & 0xff, value & 0xff); +} + +function log2(x) { + var n = 1, i = 0; + while (x > n) { + n <<= 1; + i++; + } + return i; +} + +function readInt8(data, start) { + return (data[start] << 24) >> 24; +} + +function readUint16(data, offset) { + return (data[offset] << 8) | data[offset + 1]; +} + +function readUint32(data, offset) { + return ((data[offset] << 24) | (data[offset + 1] << 16) | + (data[offset + 2] << 8) | data[offset + 3]) >>> 0; +} + +// Lazy test the endianness of the platform +// NOTE: This will be 'true' for simulated TypedArrays +function isLittleEndian() { + var buffer8 = new Uint8Array(2); + buffer8[0] = 1; + var buffer16 = new Uint16Array(buffer8.buffer); + return (buffer16[0] === 1); +} + +Object.defineProperty(PDFJS, 'isLittleEndian', { + configurable: true, + get: function PDFJS_isLittleEndian() { + return shadow(PDFJS, 'isLittleEndian', isLittleEndian()); + } +}); + + // Lazy test if the userAgant support CanvasTypedArrays +function hasCanvasTypedArrays() { + var canvas = document.createElement('canvas'); + canvas.width = canvas.height = 1; + var ctx = canvas.getContext('2d'); + var imageData = ctx.createImageData(1, 1); + return (typeof imageData.data.buffer !== 'undefined'); +} + +Object.defineProperty(PDFJS, 'hasCanvasTypedArrays', { + configurable: true, + get: function PDFJS_hasCanvasTypedArrays() { + return shadow(PDFJS, 'hasCanvasTypedArrays', hasCanvasTypedArrays()); + } +}); + +var Uint32ArrayView = (function Uint32ArrayViewClosure() { + + function Uint32ArrayView(buffer, length) { + this.buffer = buffer; + this.byteLength = buffer.length; + this.length = length === undefined ? (this.byteLength >> 2) : length; + ensureUint32ArrayViewProps(this.length); + } + Uint32ArrayView.prototype = Object.create(null); + + var uint32ArrayViewSetters = 0; + function createUint32ArrayProp(index) { + return { + get: function () { + var buffer = this.buffer, offset = index << 2; + return (buffer[offset] | (buffer[offset + 1] << 8) | + (buffer[offset + 2] << 16) | (buffer[offset + 3] << 24)) >>> 0; + }, + set: function (value) { + var buffer = this.buffer, offset = index << 2; + buffer[offset] = value & 255; + buffer[offset + 1] = (value >> 8) & 255; + buffer[offset + 2] = (value >> 16) & 255; + buffer[offset + 3] = (value >>> 24) & 255; + } + }; + } + + function ensureUint32ArrayViewProps(length) { + while (uint32ArrayViewSetters < length) { + Object.defineProperty(Uint32ArrayView.prototype, + uint32ArrayViewSetters, + createUint32ArrayProp(uint32ArrayViewSetters)); + uint32ArrayViewSetters++; + } + } + + return Uint32ArrayView; +})(); + var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0]; var Util = PDFJS.Util = (function UtilClosure() { @@ -448,11 +557,6 @@ var Util = PDFJS.Util = (function UtilClosure() { return 'rgb(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ')'; }; - Util.makeCssCmyk = function Util_makeCssCmyk(cmyk) { - var rgb = ColorSpace.singletons.cmyk.getRgb(cmyk, 0); - return Util.makeCssRgb(rgb); - }; - // Concatenates two transformation matrices together and returns the result. Util.transform = function Util_transform(m1, m2) { return [ @@ -652,7 +756,22 @@ var Util = PDFJS.Util = (function UtilClosure() { return Util; })(); +/** + * PDF page viewport created based on scale, rotation and offset. + * @class + * @alias PDFJS.PageViewport + */ var PageViewport = PDFJS.PageViewport = (function PageViewportClosure() { + /** + * @constructor + * @private + * @param viewBox {Array} xMin, yMin, xMax and yMax coordinates. + * @param scale {number} scale of the viewport. + * @param rotation {number} rotations of the viewport in degrees. + * @param offsetX {number} offset X + * @param offsetY {number} offset Y + * @param dontFlip {boolean} if true, axis Y will not be flipped. + */ function PageViewport(viewBox, scale, rotation, offsetX, offsetY, dontFlip) { this.viewBox = viewBox; this.scale = scale; @@ -716,7 +835,14 @@ var PageViewport = PDFJS.PageViewport = (function PageViewportClosure() { this.height = height; this.fontScale = scale; } - PageViewport.prototype = { + PageViewport.prototype = /** @lends PDFJS.PageViewport.prototype */ { + /** + * Clones viewport with additional properties. + * @param args {Object} (optional) If specified, may contain the 'scale' or + * 'rotation' properties to override the corresponding properties in + * the cloned viewport. + * @returns {PDFJS.PageViewport} Cloned viewport. + */ clone: function PageViewPort_clone(args) { args = args || {}; var scale = 'scale' in args ? args.scale : this.scale; @@ -724,15 +850,41 @@ var PageViewport = PDFJS.PageViewport = (function PageViewportClosure() { return new PageViewport(this.viewBox.slice(), scale, rotation, this.offsetX, this.offsetY, args.dontFlip); }, + /** + * Converts PDF point to the viewport coordinates. For examples, useful for + * converting PDF location into canvas pixel coordinates. + * @param x {number} X coordinate. + * @param y {number} Y coordinate. + * @returns {Object} Object that contains 'x' and 'y' properties of the + * point in the viewport coordinate space. + * @see {@link convertToPdfPoint} + * @see {@link convertToViewportRectangle} + */ convertToViewportPoint: function PageViewport_convertToViewportPoint(x, y) { return Util.applyTransform([x, y], this.transform); }, + /** + * Converts PDF rectangle to the viewport coordinates. + * @param rect {Array} xMin, yMin, xMax and yMax coordinates. + * @returns {Array} Contains corresponding coordinates of the rectangle + * in the viewport coordinate space. + * @see {@link convertToViewportPoint} + */ convertToViewportRectangle: function PageViewport_convertToViewportRectangle(rect) { var tl = Util.applyTransform([rect[0], rect[1]], this.transform); var br = Util.applyTransform([rect[2], rect[3]], this.transform); return [tl[0], tl[1], br[0], br[1]]; }, + /** + * Converts viewport coordinates to the PDF location. For examples, useful + * for converting canvas pixel location into PDF one. + * @param x {number} X coordinate. + * @param y {number} Y coordinate. + * @returns {Object} Object that contains 'x' and 'y' properties of the + * point in the PDF coordinate space. + * @see {@link convertToViewportPoint} + */ convertToPdfPoint: function PageViewport_convertToPdfPoint(x, y) { return Util.applyInverseTransform([x, y], this.transform); } @@ -837,40 +989,36 @@ function isRef(v) { return v instanceof Ref; } -function isPDFFunction(v) { - var fnDict; - if (typeof v != 'object') { - return false; - } else if (isDict(v)) { - fnDict = v; - } else if (isStream(v)) { - fnDict = v.dict; - } else { - return false; - } - return fnDict.has('FunctionType'); -} +/** + * Promise Capability object. + * + * @typedef {Object} PromiseCapability + * @property {Promise} promise - A promise object. + * @property {function} resolve - Fullfills the promise. + * @property {function} reject - Rejects the promise. + */ /** - * Legacy support for PDFJS Promise implementation. - * TODO remove eventually + * Creates a promise capability object. + * @alias PDFJS.createPromiseCapability + * + * @return {PromiseCapability} A capability object contains: + * - a Promise, resolve and reject methods. */ -var LegacyPromise = PDFJS.LegacyPromise = (function LegacyPromiseClosure() { - return function LegacyPromise() { - var resolve, reject; - var promise = new Promise(function (resolve_, reject_) { - resolve = resolve_; - reject = reject_; - }); - promise.resolve = resolve; - promise.reject = reject; - return promise; - }; -})(); +function createPromiseCapability() { + var capability = {}; + capability.promise = new Promise(function (resolve, reject) { + capability.resolve = resolve; + capability.reject = reject; + }); + return capability; +} + +PDFJS.createPromiseCapability = createPromiseCapability; /** * Polyfill for Promises: - * The following promise implementation tries to generally implment the + * The following promise implementation tries to generally implement the * Promise/A+ spec. Some notable differences from other promise libaries are: * - There currently isn't a seperate deferred and promise object. * - Unhandled rejections eventually show an error if they aren't handled. @@ -905,8 +1053,20 @@ var LegacyPromise = PDFJS.LegacyPromise = (function LegacyPromiseClosure() { }; } if (typeof globalScope.Promise.resolve !== 'function') { - globalScope.Promise.resolve = function (x) { - return new globalScope.Promise(function (resolve) { resolve(x); }); + globalScope.Promise.resolve = function (value) { + return new globalScope.Promise(function (resolve) { resolve(value); }); + }; + } + if (typeof globalScope.Promise.reject !== 'function') { + globalScope.Promise.reject = function (reason) { + return new globalScope.Promise(function (resolve, reject) { + reject(reason); + }); + }; + } + if (typeof globalScope.Promise.prototype.catch !== 'function') { + globalScope.Promise.prototype.catch = function (onReject) { + return globalScope.Promise.prototype.then(undefined, onReject); }; } return; @@ -1031,7 +1191,11 @@ var LegacyPromise = PDFJS.LegacyPromise = (function LegacyPromiseClosure() { function Promise(resolver) { this._status = STATUS_PENDING; this._handlers = []; - resolver.call(this, this._resolve.bind(this), this._reject.bind(this)); + try { + resolver.call(this, this._resolve.bind(this), this._reject.bind(this)); + } catch (e) { + this._reject(e); + } } /** * Builds a promise that is resolved when all the passed in promises are @@ -1083,18 +1247,28 @@ var LegacyPromise = PDFJS.LegacyPromise = (function LegacyPromiseClosure() { /** * Checks if the value is likely a promise (has a 'then' function). - * @return {boolean} true if x is thenable + * @return {boolean} true if value is thenable */ Promise.isPromise = function Promise_isPromise(value) { return value && typeof value.then === 'function'; }; + /** * Creates resolved promise - * @param x resolve value + * @param value resolve value * @returns {Promise} */ - Promise.resolve = function Promise_resolve(x) { - return new Promise(function (resolve) { resolve(x); }); + Promise.resolve = function Promise_resolve(value) { + return new Promise(function (resolve) { resolve(value); }); + }; + + /** + * Creates rejected promise + * @param reason rejection value + * @returns {Promise} + */ + Promise.reject = function Promise_reject(reason) { + return new Promise(function (resolve, reject) { reject(reason); }); }; Promise.prototype = { @@ -1148,6 +1322,10 @@ var LegacyPromise = PDFJS.LegacyPromise = (function LegacyPromiseClosure() { }); HandlerManager.scheduleHandlers(this); return nextPromise; + }, + + catch: function Promise_catch(onReject) { + return this.then(undefined, onReject); } }; @@ -1192,17 +1370,18 @@ var StatTimer = (function StatTimerClosure() { delete this.started[name]; }, toString: function StatTimer_toString() { + var i, ii; var times = this.times; var out = ''; // Find the longest name for padding purposes. var longest = 0; - for (var i = 0, ii = times.length; i < ii; ++i) { + for (i = 0, ii = times.length; i < ii; ++i) { var name = times[i]['name']; if (name.length > longest) { longest = name.length; } } - for (var i = 0, ii = times.length; i < ii; ++i) { + for (i = 0, ii = times.length; i < ii; ++i) { var span = times[i]; var duration = span.end - span.start; out += rpad(span['name'], ' ', longest) + ' ' + duration + 'ms\n'; @@ -1254,7 +1433,7 @@ function MessageHandler(name, comObj) { this.comObj = comObj; this.callbackIndex = 1; this.postMessageTransfers = true; - var callbacks = this.callbacks = {}; + var callbacksCapabilities = this.callbacksCapabilities = {}; var ah = this.actionHandler = {}; ah['console_log'] = [function ahConsoleLog(data) { @@ -1271,35 +1450,40 @@ function MessageHandler(name, comObj) { var data = event.data; if (data.isReply) { var callbackId = data.callbackId; - if (data.callbackId in callbacks) { - var callback = callbacks[callbackId]; - delete callbacks[callbackId]; - callback(data.data); + if (data.callbackId in callbacksCapabilities) { + var callback = callbacksCapabilities[callbackId]; + delete callbacksCapabilities[callbackId]; + if ('error' in data) { + callback.reject(data.error); + } else { + callback.resolve(data.data); + } } else { error('Cannot resolve callback ' + callbackId); } } else if (data.action in ah) { var action = ah[data.action]; if (data.callbackId) { - var deferred = {}; - var promise = new Promise(function (resolve, reject) { - deferred.resolve = resolve; - deferred.reject = reject; - }); - deferred.promise = promise; - promise.then(function(resolvedData) { + Promise.resolve().then(function () { + return action[0].call(action[1], data.data); + }).then(function (result) { + comObj.postMessage({ + isReply: true, + callbackId: data.callbackId, + data: result + }); + }, function (reason) { comObj.postMessage({ isReply: true, callbackId: data.callbackId, - data: resolvedData + error: reason }); }); - action[0].call(action[1], data.data, deferred); } else { action[0].call(action[1], data.data); } } else { - error('Unkown action from worker: ' + data.action); + error('Unknown action from worker: ' + data.action); } }; } @@ -1316,19 +1500,47 @@ MessageHandler.prototype = { * Sends a message to the comObj to invoke the action with the supplied data. * @param {String} actionName Action to call. * @param {JSON} data JSON data to send. - * @param {function} [callback] Optional callback that will handle a reply. * @param {Array} [transfers] Optional list of transfers/ArrayBuffers */ - send: function messageHandlerSend(actionName, data, callback, transfers) { + send: function messageHandlerSend(actionName, data, transfers) { var message = { action: actionName, data: data }; - if (callback) { - var callbackId = this.callbackIndex++; - this.callbacks[callbackId] = callback; - message.callbackId = callbackId; + this.postMessage(message, transfers); + }, + /** + * Sends a message to the comObj to invoke the action with the supplied data. + * Expects that other side will callback with the response. + * @param {String} actionName Action to call. + * @param {JSON} data JSON data to send. + * @param {Array} [transfers] Optional list of transfers/ArrayBuffers. + * @returns {Promise} Promise to be resolved with response data. + */ + sendWithPromise: + function messageHandlerSendWithPromise(actionName, data, transfers) { + var callbackId = this.callbackIndex++; + var message = { + action: actionName, + data: data, + callbackId: callbackId + }; + var capability = createPromiseCapability(); + this.callbacksCapabilities[callbackId] = capability; + try { + this.postMessage(message, transfers); + } catch (e) { + capability.reject(e); } + return capability.promise; + }, + /** + * Sends raw message to the comObj. + * @private + * @param message {Object} Raw message. + * @param transfers List of transfers/ArrayBuffers, or undefined. + */ + postMessage: function (message, transfers) { if (transfers && this.postMessageTransfers) { this.comObj.postMessage(message, transfers); } else { @@ -1346,1635 +1558,7 @@ function loadJpegStream(id, imageUrl, objs) { } -var ColorSpace = (function ColorSpaceClosure() { - // Constructor should define this.numComps, this.defaultColor, this.name - function ColorSpace() { - error('should not call ColorSpace constructor'); - } - - ColorSpace.prototype = { - /** - * Converts the color value to the RGB color. The color components are - * located in the src array starting from the srcOffset. Returns the array - * of the rgb components, each value ranging from [0,255]. - */ - getRgb: function ColorSpace_getRgb(src, srcOffset) { - var rgb = new Uint8Array(3); - this.getRgbItem(src, srcOffset, rgb, 0); - return rgb; - }, - /** - * Converts the color value to the RGB color, similar to the getRgb method. - * The result placed into the dest array starting from the destOffset. - */ - getRgbItem: function ColorSpace_getRgbItem(src, srcOffset, - dest, destOffset) { - error('Should not call ColorSpace.getRgbItem'); - }, - /** - * Converts the specified number of the color values to the RGB colors. - * The colors are located in the src array starting from the srcOffset. - * The result is placed into the dest array starting from the destOffset. - * The src array items shall be in [0,2^bits) range, the dest array items - * will be in [0,255] range. alpha01 indicates how many alpha components - * there are in the dest array; it will be either 0 (RGB array) or 1 (RGBA - * array). - */ - getRgbBuffer: function ColorSpace_getRgbBuffer(src, srcOffset, count, - dest, destOffset, bits, - alpha01) { - error('Should not call ColorSpace.getRgbBuffer'); - }, - /** - * Determines the number of bytes required to store the result of the - * conversion done by the getRgbBuffer method. As in getRgbBuffer, - * |alpha01| is either 0 (RGB output) or 1 (RGBA output). - */ - getOutputLength: function ColorSpace_getOutputLength(inputLength, - alpha01) { - error('Should not call ColorSpace.getOutputLength'); - }, - /** - * Returns true if source data will be equal the result/output data. - */ - isPassthrough: function ColorSpace_isPassthrough(bits) { - return false; - }, - /** - * Fills in the RGB colors in the destination buffer. alpha01 indicates - * how many alpha components there are in the dest array; it will be either - * 0 (RGB array) or 1 (RGBA array). - */ - fillRgb: function ColorSpace_fillRgb(dest, originalWidth, - originalHeight, width, height, - actualHeight, bpc, comps, alpha01) { - var count = originalWidth * originalHeight; - var rgbBuf = null; - var numComponentColors = 1 << bpc; - var needsResizing = originalHeight != height || originalWidth != width; - - if (this.isPassthrough(bpc)) { - rgbBuf = comps; - - } else if (this.numComps === 1 && count > numComponentColors && - this.name !== 'DeviceGray' && this.name !== 'DeviceRGB') { - // Optimization: create a color map when there is just one component and - // we are converting more colors than the size of the color map. We - // don't build the map if the colorspace is gray or rgb since those - // methods are faster than building a map. This mainly offers big speed - // ups for indexed and alternate colorspaces. - // - // TODO it may be worth while to cache the color map. While running - // testing I never hit a cache so I will leave that out for now (perhaps - // we are reparsing colorspaces too much?). - var allColors = bpc <= 8 ? new Uint8Array(numComponentColors) : - new Uint16Array(numComponentColors); - for (var i = 0; i < numComponentColors; i++) { - allColors[i] = i; - } - var colorMap = new Uint8Array(numComponentColors * 3); - this.getRgbBuffer(allColors, 0, numComponentColors, colorMap, 0, bpc, - /* alpha01 = */ 0); - - if (!needsResizing) { - // Fill in the RGB values directly into |dest|. - var destPos = 0; - for (var i = 0; i < count; ++i) { - var key = comps[i] * 3; - dest[destPos++] = colorMap[key]; - dest[destPos++] = colorMap[key + 1]; - dest[destPos++] = colorMap[key + 2]; - destPos += alpha01; - } - } else { - rgbBuf = new Uint8Array(count * 3); - var rgbPos = 0; - for (var i = 0; i < count; ++i) { - var key = comps[i] * 3; - rgbBuf[rgbPos++] = colorMap[key]; - rgbBuf[rgbPos++] = colorMap[key + 1]; - rgbBuf[rgbPos++] = colorMap[key + 2]; - } - } - } else { - if (!needsResizing) { - // Fill in the RGB values directly into |dest|. - this.getRgbBuffer(comps, 0, width * actualHeight, dest, 0, bpc, - alpha01); - } else { - rgbBuf = new Uint8Array(count * 3); - this.getRgbBuffer(comps, 0, count, rgbBuf, 0, bpc, - /* alpha01 = */ 0); - } - } - - if (rgbBuf) { - if (needsResizing) { - rgbBuf = PDFImage.resize(rgbBuf, bpc, 3, originalWidth, - originalHeight, width, height); - } - var rgbPos = 0; - var destPos = 0; - for (var i = 0, ii = width * actualHeight; i < ii; i++) { - dest[destPos++] = rgbBuf[rgbPos++]; - dest[destPos++] = rgbBuf[rgbPos++]; - dest[destPos++] = rgbBuf[rgbPos++]; - destPos += alpha01; - } - } - }, - /** - * True if the colorspace has components in the default range of [0, 1]. - * This should be true for all colorspaces except for lab color spaces - * which are [0,100], [-128, 127], [-128, 127]. - */ - usesZeroToOneRange: true - }; - - ColorSpace.parse = function ColorSpace_parse(cs, xref, res) { - var IR = ColorSpace.parseToIR(cs, xref, res); - if (IR instanceof AlternateCS) { - return IR; - } - return ColorSpace.fromIR(IR); - }; - - ColorSpace.fromIR = function ColorSpace_fromIR(IR) { - var name = isArray(IR) ? IR[0] : IR; - - switch (name) { - case 'DeviceGrayCS': - return this.singletons.gray; - case 'DeviceRgbCS': - return this.singletons.rgb; - case 'DeviceCmykCS': - return this.singletons.cmyk; - case 'CalGrayCS': - var whitePoint = IR[1].WhitePoint; - var blackPoint = IR[1].BlackPoint; - var gamma = IR[1].Gamma; - return new CalGrayCS(whitePoint, blackPoint, gamma); - case 'PatternCS': - var basePatternCS = IR[1]; - if (basePatternCS) { - basePatternCS = ColorSpace.fromIR(basePatternCS); - } - return new PatternCS(basePatternCS); - case 'IndexedCS': - var baseIndexedCS = IR[1]; - var hiVal = IR[2]; - var lookup = IR[3]; - return new IndexedCS(ColorSpace.fromIR(baseIndexedCS), hiVal, lookup); - case 'AlternateCS': - var numComps = IR[1]; - var alt = IR[2]; - var tintFnIR = IR[3]; - - return new AlternateCS(numComps, ColorSpace.fromIR(alt), - PDFFunction.fromIR(tintFnIR)); - case 'LabCS': - var whitePoint = IR[1].WhitePoint; - var blackPoint = IR[1].BlackPoint; - var range = IR[1].Range; - return new LabCS(whitePoint, blackPoint, range); - default: - error('Unkown name ' + name); - } - return null; - }; - - ColorSpace.parseToIR = function ColorSpace_parseToIR(cs, xref, res) { - if (isName(cs)) { - var colorSpaces = res.get('ColorSpace'); - if (isDict(colorSpaces)) { - var refcs = colorSpaces.get(cs.name); - if (refcs) { - cs = refcs; - } - } - } - - cs = xref.fetchIfRef(cs); - var mode; - - if (isName(cs)) { - mode = cs.name; - this.mode = mode; - - switch (mode) { - case 'DeviceGray': - case 'G': - return 'DeviceGrayCS'; - case 'DeviceRGB': - case 'RGB': - return 'DeviceRgbCS'; - case 'DeviceCMYK': - case 'CMYK': - return 'DeviceCmykCS'; - case 'Pattern': - return ['PatternCS', null]; - default: - error('unrecognized colorspace ' + mode); - } - } else if (isArray(cs)) { - mode = cs[0].name; - this.mode = mode; - - switch (mode) { - case 'DeviceGray': - case 'G': - return 'DeviceGrayCS'; - case 'DeviceRGB': - case 'RGB': - return 'DeviceRgbCS'; - case 'DeviceCMYK': - case 'CMYK': - return 'DeviceCmykCS'; - case 'CalGray': - var params = cs[1].getAll(); - return ['CalGrayCS', params]; - case 'CalRGB': - return 'DeviceRgbCS'; - case 'ICCBased': - var stream = xref.fetchIfRef(cs[1]); - var dict = stream.dict; - var numComps = dict.get('N'); - if (numComps == 1) { - return 'DeviceGrayCS'; - } else if (numComps == 3) { - return 'DeviceRgbCS'; - } else if (numComps == 4) { - return 'DeviceCmykCS'; - } - break; - case 'Pattern': - var basePatternCS = cs[1]; - if (basePatternCS) { - basePatternCS = ColorSpace.parseToIR(basePatternCS, xref, res); - } - return ['PatternCS', basePatternCS]; - case 'Indexed': - case 'I': - var baseIndexedCS = ColorSpace.parseToIR(cs[1], xref, res); - var hiVal = cs[2] + 1; - var lookup = xref.fetchIfRef(cs[3]); - if (isStream(lookup)) { - lookup = lookup.getBytes(); - } - return ['IndexedCS', baseIndexedCS, hiVal, lookup]; - case 'Separation': - case 'DeviceN': - var name = cs[1]; - var numComps = 1; - if (isName(name)) { - numComps = 1; - } else if (isArray(name)) { - numComps = name.length; - } - var alt = ColorSpace.parseToIR(cs[2], xref, res); - var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3])); - return ['AlternateCS', numComps, alt, tintFnIR]; - case 'Lab': - var params = cs[1].getAll(); - return ['LabCS', params]; - default: - error('unimplemented color space object "' + mode + '"'); - } - } else { - error('unrecognized color space object: "' + cs + '"'); - } - return null; - }; - /** - * Checks if a decode map matches the default decode map for a color space. - * This handles the general decode maps where there are two values per - * component. e.g. [0, 1, 0, 1, 0, 1] for a RGB color. - * This does not handle Lab, Indexed, or Pattern decode maps since they are - * slightly different. - * @param {Array} decode Decode map (usually from an image). - * @param {Number} n Number of components the color space has. - */ - ColorSpace.isDefaultDecode = function ColorSpace_isDefaultDecode(decode, n) { - if (!decode) { - return true; - } - - if (n * 2 !== decode.length) { - warn('The decode map is not the correct length'); - return true; - } - for (var i = 0, ii = decode.length; i < ii; i += 2) { - if (decode[i] !== 0 || decode[i + 1] != 1) { - return false; - } - } - return true; - }; - - ColorSpace.singletons = { - get gray() { - return shadow(this, 'gray', new DeviceGrayCS()); - }, - get rgb() { - return shadow(this, 'rgb', new DeviceRgbCS()); - }, - get cmyk() { - return shadow(this, 'cmyk', new DeviceCmykCS()); - } - }; - - return ColorSpace; -})(); - -/** - * Alternate color space handles both Separation and DeviceN color spaces. A - * Separation color space is actually just a DeviceN with one color component. - * Both color spaces use a tinting function to convert colors to a base color - * space. - */ -var AlternateCS = (function AlternateCSClosure() { - function AlternateCS(numComps, base, tintFn) { - this.name = 'Alternate'; - this.numComps = numComps; - this.defaultColor = new Float32Array(numComps); - for (var i = 0; i < numComps; ++i) { - this.defaultColor[i] = 1; - } - this.base = base; - this.tintFn = tintFn; - } - - AlternateCS.prototype = { - getRgb: ColorSpace.prototype.getRgb, - getRgbItem: function AlternateCS_getRgbItem(src, srcOffset, - dest, destOffset) { - var baseNumComps = this.base.numComps; - var input = 'subarray' in src ? - src.subarray(srcOffset, srcOffset + this.numComps) : - Array.prototype.slice.call(src, srcOffset, srcOffset + this.numComps); - var tinted = this.tintFn(input); - this.base.getRgbItem(tinted, 0, dest, destOffset); - }, - getRgbBuffer: function AlternateCS_getRgbBuffer(src, srcOffset, count, - dest, destOffset, bits, - alpha01) { - var tintFn = this.tintFn; - var base = this.base; - var scale = 1 / ((1 << bits) - 1); - var baseNumComps = base.numComps; - var usesZeroToOneRange = base.usesZeroToOneRange; - var isPassthrough = (base.isPassthrough(8) || !usesZeroToOneRange) && - alpha01 === 0; - var pos = isPassthrough ? destOffset : 0; - var baseBuf = isPassthrough ? dest : new Uint8Array(baseNumComps * count); - var numComps = this.numComps; - - var scaled = new Float32Array(numComps); - for (var i = 0; i < count; i++) { - for (var j = 0; j < numComps; j++) { - scaled[j] = src[srcOffset++] * scale; - } - var tinted = tintFn(scaled); - if (usesZeroToOneRange) { - for (var j = 0; j < baseNumComps; j++) { - baseBuf[pos++] = tinted[j] * 255; - } - } else { - base.getRgbItem(tinted, 0, baseBuf, pos); - pos += baseNumComps; - } - } - if (!isPassthrough) { - base.getRgbBuffer(baseBuf, 0, count, dest, destOffset, 8, alpha01); - } - }, - getOutputLength: function AlternateCS_getOutputLength(inputLength, - alpha01) { - return this.base.getOutputLength(inputLength * - this.base.numComps / this.numComps, - alpha01); - }, - isPassthrough: ColorSpace.prototype.isPassthrough, - fillRgb: ColorSpace.prototype.fillRgb, - isDefaultDecode: function AlternateCS_isDefaultDecode(decodeMap) { - return ColorSpace.isDefaultDecode(decodeMap, this.numComps); - }, - usesZeroToOneRange: true - }; - - return AlternateCS; -})(); - -var PatternCS = (function PatternCSClosure() { - function PatternCS(baseCS) { - this.name = 'Pattern'; - this.base = baseCS; - } - PatternCS.prototype = {}; - - return PatternCS; -})(); - -var IndexedCS = (function IndexedCSClosure() { - function IndexedCS(base, highVal, lookup) { - this.name = 'Indexed'; - this.numComps = 1; - this.defaultColor = new Uint8Array([0]); - this.base = base; - this.highVal = highVal; - - var baseNumComps = base.numComps; - var length = baseNumComps * highVal; - var lookupArray; - - if (isStream(lookup)) { - lookupArray = new Uint8Array(length); - var bytes = lookup.getBytes(length); - lookupArray.set(bytes); - } else if (isString(lookup)) { - lookupArray = new Uint8Array(length); - for (var i = 0; i < length; ++i) { - lookupArray[i] = lookup.charCodeAt(i); - } - } else if (lookup instanceof Uint8Array || lookup instanceof Array) { - lookupArray = lookup; - } else { - error('Unrecognized lookup table: ' + lookup); - } - this.lookup = lookupArray; - } - - IndexedCS.prototype = { - getRgb: ColorSpace.prototype.getRgb, - getRgbItem: function IndexedCS_getRgbItem(src, srcOffset, - dest, destOffset) { - var numComps = this.base.numComps; - var start = src[srcOffset] * numComps; - this.base.getRgbItem(this.lookup, start, dest, destOffset); - }, - getRgbBuffer: function IndexedCS_getRgbBuffer(src, srcOffset, count, - dest, destOffset, bits, - alpha01) { - var base = this.base; - var numComps = base.numComps; - var outputDelta = base.getOutputLength(numComps, alpha01); - var lookup = this.lookup; - - for (var i = 0; i < count; ++i) { - var lookupPos = src[srcOffset++] * numComps; - base.getRgbBuffer(lookup, lookupPos, 1, dest, destOffset, 8, alpha01); - destOffset += outputDelta; - } - }, - getOutputLength: function IndexedCS_getOutputLength(inputLength, alpha01) { - return this.base.getOutputLength(inputLength * this.base.numComps, - alpha01); - }, - isPassthrough: ColorSpace.prototype.isPassthrough, - fillRgb: ColorSpace.prototype.fillRgb, - isDefaultDecode: function IndexedCS_isDefaultDecode(decodeMap) { - // indexed color maps shouldn't be changed - return true; - }, - usesZeroToOneRange: true - }; - return IndexedCS; -})(); - -var DeviceGrayCS = (function DeviceGrayCSClosure() { - function DeviceGrayCS() { - this.name = 'DeviceGray'; - this.numComps = 1; - this.defaultColor = new Float32Array([0]); - } - - DeviceGrayCS.prototype = { - getRgb: ColorSpace.prototype.getRgb, - getRgbItem: function DeviceGrayCS_getRgbItem(src, srcOffset, - dest, destOffset) { - var c = (src[srcOffset] * 255) | 0; - c = c < 0 ? 0 : c > 255 ? 255 : c; - dest[destOffset] = dest[destOffset + 1] = dest[destOffset + 2] = c; - }, - getRgbBuffer: function DeviceGrayCS_getRgbBuffer(src, srcOffset, count, - dest, destOffset, bits, - alpha01) { - var scale = 255 / ((1 << bits) - 1); - var j = srcOffset, q = destOffset; - for (var i = 0; i < count; ++i) { - var c = (scale * src[j++]) | 0; - dest[q++] = c; - dest[q++] = c; - dest[q++] = c; - q += alpha01; - } - }, - getOutputLength: function DeviceGrayCS_getOutputLength(inputLength, - alpha01) { - return inputLength * (3 + alpha01); - }, - isPassthrough: ColorSpace.prototype.isPassthrough, - fillRgb: ColorSpace.prototype.fillRgb, - isDefaultDecode: function DeviceGrayCS_isDefaultDecode(decodeMap) { - return ColorSpace.isDefaultDecode(decodeMap, this.numComps); - }, - usesZeroToOneRange: true - }; - return DeviceGrayCS; -})(); - -var DeviceRgbCS = (function DeviceRgbCSClosure() { - function DeviceRgbCS() { - this.name = 'DeviceRGB'; - this.numComps = 3; - this.defaultColor = new Float32Array([0, 0, 0]); - } - DeviceRgbCS.prototype = { - getRgb: ColorSpace.prototype.getRgb, - getRgbItem: function DeviceRgbCS_getRgbItem(src, srcOffset, - dest, destOffset) { - var r = (src[srcOffset] * 255) | 0; - var g = (src[srcOffset + 1] * 255) | 0; - var b = (src[srcOffset + 2] * 255) | 0; - dest[destOffset] = r < 0 ? 0 : r > 255 ? 255 : r; - dest[destOffset + 1] = g < 0 ? 0 : g > 255 ? 255 : g; - dest[destOffset + 2] = b < 0 ? 0 : b > 255 ? 255 : b; - }, - getRgbBuffer: function DeviceRgbCS_getRgbBuffer(src, srcOffset, count, - dest, destOffset, bits, - alpha01) { - if (bits === 8 && alpha01 === 0) { - dest.set(src.subarray(srcOffset, srcOffset + count * 3), destOffset); - return; - } - var scale = 255 / ((1 << bits) - 1); - var j = srcOffset, q = destOffset; - for (var i = 0; i < count; ++i) { - dest[q++] = (scale * src[j++]) | 0; - dest[q++] = (scale * src[j++]) | 0; - dest[q++] = (scale * src[j++]) | 0; - q += alpha01; - } - }, - getOutputLength: function DeviceRgbCS_getOutputLength(inputLength, - alpha01) { - return (inputLength * (3 + alpha01) / 3) | 0; - }, - isPassthrough: function DeviceRgbCS_isPassthrough(bits) { - return bits == 8; - }, - fillRgb: ColorSpace.prototype.fillRgb, - isDefaultDecode: function DeviceRgbCS_isDefaultDecode(decodeMap) { - return ColorSpace.isDefaultDecode(decodeMap, this.numComps); - }, - usesZeroToOneRange: true - }; - return DeviceRgbCS; -})(); - -var DeviceCmykCS = (function DeviceCmykCSClosure() { - // The coefficients below was found using numerical analysis: the method of - // steepest descent for the sum((f_i - color_value_i)^2) for r/g/b colors, - // where color_value is the tabular value from the table of sampled RGB colors - // from CMYK US Web Coated (SWOP) colorspace, and f_i is the corresponding - // CMYK color conversion using the estimation below: - // f(A, B,.. N) = Acc+Bcm+Ccy+Dck+c+Fmm+Gmy+Hmk+Im+Jyy+Kyk+Ly+Mkk+Nk+255 - function convertToRgb(src, srcOffset, srcScale, dest, destOffset) { - var c = src[srcOffset + 0] * srcScale; - var m = src[srcOffset + 1] * srcScale; - var y = src[srcOffset + 2] * srcScale; - var k = src[srcOffset + 3] * srcScale; - - var r = - c * (-4.387332384609988 * c + 54.48615194189176 * m + - 18.82290502165302 * y + 212.25662451639585 * k + - -285.2331026137004) + - m * (1.7149763477362134 * m - 5.6096736904047315 * y + - -17.873870861415444 * k - 5.497006427196366) + - y * (-2.5217340131683033 * y - 21.248923337353073 * k + - 17.5119270841813) + - k * (-21.86122147463605 * k - 189.48180835922747) + 255; - var g = - c * (8.841041422036149 * c + 60.118027045597366 * m + - 6.871425592049007 * y + 31.159100130055922 * k + - -79.2970844816548) + - m * (-15.310361306967817 * m + 17.575251261109482 * y + - 131.35250912493976 * k - 190.9453302588951) + - y * (4.444339102852739 * y + 9.8632861493405 * k - 24.86741582555878) + - k * (-20.737325471181034 * k - 187.80453709719578) + 255; - var b = - c * (0.8842522430003296 * c + 8.078677503112928 * m + - 30.89978309703729 * y - 0.23883238689178934 * k + - -14.183576799673286) + - m * (10.49593273432072 * m + 63.02378494754052 * y + - 50.606957656360734 * k - 112.23884253719248) + - y * (0.03296041114873217 * y + 115.60384449646641 * k + - -193.58209356861505) + - k * (-22.33816807309886 * k - 180.12613974708367) + 255; - - dest[destOffset] = r > 255 ? 255 : r < 0 ? 0 : r; - dest[destOffset + 1] = g > 255 ? 255 : g < 0 ? 0 : g; - dest[destOffset + 2] = b > 255 ? 255 : b < 0 ? 0 : b; - } - - function DeviceCmykCS() { - this.name = 'DeviceCMYK'; - this.numComps = 4; - this.defaultColor = new Float32Array([0, 0, 0, 1]); - } - DeviceCmykCS.prototype = { - getRgb: ColorSpace.prototype.getRgb, - getRgbItem: function DeviceCmykCS_getRgbItem(src, srcOffset, - dest, destOffset) { - convertToRgb(src, srcOffset, 1, dest, destOffset); - }, - getRgbBuffer: function DeviceCmykCS_getRgbBuffer(src, srcOffset, count, - dest, destOffset, bits, - alpha01) { - var scale = 1 / ((1 << bits) - 1); - for (var i = 0; i < count; i++) { - convertToRgb(src, srcOffset, scale, dest, destOffset); - srcOffset += 4; - destOffset += 3 + alpha01; - } - }, - getOutputLength: function DeviceCmykCS_getOutputLength(inputLength, - alpha01) { - return (inputLength / 4 * (3 + alpha01)) | 0; - }, - isPassthrough: ColorSpace.prototype.isPassthrough, - fillRgb: ColorSpace.prototype.fillRgb, - isDefaultDecode: function DeviceCmykCS_isDefaultDecode(decodeMap) { - return ColorSpace.isDefaultDecode(decodeMap, this.numComps); - }, - usesZeroToOneRange: true - }; - - return DeviceCmykCS; -})(); - -// -// CalGrayCS: Based on "PDF Reference, Sixth Ed", p.245 -// -var CalGrayCS = (function CalGrayCSClosure() { - function CalGrayCS(whitePoint, blackPoint, gamma) { - this.name = 'CalGray'; - this.numComps = 1; - this.defaultColor = new Float32Array([0]); - - if (!whitePoint) { - error('WhitePoint missing - required for color space CalGray'); - } - blackPoint = blackPoint || [0, 0, 0]; - gamma = gamma || 1; - - // Translate arguments to spec variables. - this.XW = whitePoint[0]; - this.YW = whitePoint[1]; - this.ZW = whitePoint[2]; - - this.XB = blackPoint[0]; - this.YB = blackPoint[1]; - this.ZB = blackPoint[2]; - - this.G = gamma; - - // Validate variables as per spec. - if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) { - error('Invalid WhitePoint components for ' + this.name + - ', no fallback available'); - } - - if (this.XB < 0 || this.YB < 0 || this.ZB < 0) { - info('Invalid BlackPoint for ' + this.name + ', falling back to default'); - this.XB = this.YB = this.ZB = 0; - } - - if (this.XB !== 0 || this.YB !== 0 || this.ZB !== 0) { - warn(this.name + ', BlackPoint: XB: ' + this.XB + ', YB: ' + this.YB + - ', ZB: ' + this.ZB + ', only default values are supported.'); - } - - if (this.G < 1) { - info('Invalid Gamma: ' + this.G + ' for ' + this.name + - ', falling back to default'); - this.G = 1; - } - } - - function convertToRgb(cs, src, srcOffset, dest, destOffset, scale) { - // A represents a gray component of a calibrated gray space. - // A <---> AG in the spec - var A = src[srcOffset] * scale; - var AG = Math.pow(A, cs.G); - - // Computes intermediate variables M, L, N as per spec. - // Except if other than default BlackPoint values are used. - var M = cs.XW * AG; - var L = cs.YW * AG; - var N = cs.ZW * AG; - - // Decode XYZ, as per spec. - var X = M; - var Y = L; - var Z = N; - - // http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html, Ch 4. - // This yields values in range [0, 100]. - var Lstar = Math.max(116 * Math.pow(Y, 1 / 3) - 16, 0); - - // Convert values to rgb range [0, 255]. - dest[destOffset] = Lstar * 255 / 100; - dest[destOffset + 1] = Lstar * 255 / 100; - dest[destOffset + 2] = Lstar * 255 / 100; - } - - CalGrayCS.prototype = { - getRgb: ColorSpace.prototype.getRgb, - getRgbItem: function CalGrayCS_getRgbItem(src, srcOffset, - dest, destOffset) { - convertToRgb(this, src, srcOffset, dest, destOffset, 1); - }, - getRgbBuffer: function CalGrayCS_getRgbBuffer(src, srcOffset, count, - dest, destOffset, bits, - alpha01) { - var scale = 1 / ((1 << bits) - 1); - - for (var i = 0; i < count; ++i) { - convertToRgb(this, src, srcOffset, dest, destOffset, scale); - srcOffset += 1; - destOffset += 3 + alpha01; - } - }, - getOutputLength: function CalGrayCS_getOutputLength(inputLength, alpha01) { - return inputLength * (3 + alpha01); - }, - isPassthrough: ColorSpace.prototype.isPassthrough, - fillRgb: ColorSpace.prototype.fillRgb, - isDefaultDecode: function CalGrayCS_isDefaultDecode(decodeMap) { - return ColorSpace.isDefaultDecode(decodeMap, this.numComps); - }, - usesZeroToOneRange: true - }; - return CalGrayCS; -})(); - -// -// LabCS: Based on "PDF Reference, Sixth Ed", p.250 -// -var LabCS = (function LabCSClosure() { - function LabCS(whitePoint, blackPoint, range) { - this.name = 'Lab'; - this.numComps = 3; - this.defaultColor = new Float32Array([0, 0, 0]); - - if (!whitePoint) { - error('WhitePoint missing - required for color space Lab'); - } - blackPoint = blackPoint || [0, 0, 0]; - range = range || [-100, 100, -100, 100]; - - // Translate args to spec variables - this.XW = whitePoint[0]; - this.YW = whitePoint[1]; - this.ZW = whitePoint[2]; - this.amin = range[0]; - this.amax = range[1]; - this.bmin = range[2]; - this.bmax = range[3]; - - // These are here just for completeness - the spec doesn't offer any - // formulas that use BlackPoint in Lab - this.XB = blackPoint[0]; - this.YB = blackPoint[1]; - this.ZB = blackPoint[2]; - - // Validate vars as per spec - if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) { - error('Invalid WhitePoint components, no fallback available'); - } - - if (this.XB < 0 || this.YB < 0 || this.ZB < 0) { - info('Invalid BlackPoint, falling back to default'); - this.XB = this.YB = this.ZB = 0; - } - - if (this.amin > this.amax || this.bmin > this.bmax) { - info('Invalid Range, falling back to defaults'); - this.amin = -100; - this.amax = 100; - this.bmin = -100; - this.bmax = 100; - } - } - - // Function g(x) from spec - function fn_g(x) { - if (x >= 6 / 29) { - return x * x * x; - } else { - return (108 / 841) * (x - 4 / 29); - } - } - - function decode(value, high1, low2, high2) { - return low2 + (value) * (high2 - low2) / (high1); - } - - // If decoding is needed maxVal should be 2^bits per component - 1. - function convertToRgb(cs, src, srcOffset, maxVal, dest, destOffset) { - // XXX: Lab input is in the range of [0, 100], [amin, amax], [bmin, bmax] - // not the usual [0, 1]. If a command like setFillColor is used the src - // values will already be within the correct range. However, if we are - // converting an image we have to map the values to the correct range given - // above. - // Ls,as,bs <---> L*,a*,b* in the spec - var Ls = src[srcOffset]; - var as = src[srcOffset + 1]; - var bs = src[srcOffset + 2]; - if (maxVal !== false) { - Ls = decode(Ls, maxVal, 0, 100); - as = decode(as, maxVal, cs.amin, cs.amax); - bs = decode(bs, maxVal, cs.bmin, cs.bmax); - } - - // Adjust limits of 'as' and 'bs' - as = as > cs.amax ? cs.amax : as < cs.amin ? cs.amin : as; - bs = bs > cs.bmax ? cs.bmax : bs < cs.bmin ? cs.bmin : bs; - - // Computes intermediate variables X,Y,Z as per spec - var M = (Ls + 16) / 116; - var L = M + (as / 500); - var N = M - (bs / 200); - - var X = cs.XW * fn_g(L); - var Y = cs.YW * fn_g(M); - var Z = cs.ZW * fn_g(N); - - var r, g, b; - // Using different conversions for D50 and D65 white points, - // per http://www.color.org/srgb.pdf - if (cs.ZW < 1) { - // Assuming D50 (X=0.9642, Y=1.00, Z=0.8249) - r = X * 3.1339 + Y * -1.6170 + Z * -0.4906; - g = X * -0.9785 + Y * 1.9160 + Z * 0.0333; - b = X * 0.0720 + Y * -0.2290 + Z * 1.4057; - } else { - // Assuming D65 (X=0.9505, Y=1.00, Z=1.0888) - r = X * 3.2406 + Y * -1.5372 + Z * -0.4986; - g = X * -0.9689 + Y * 1.8758 + Z * 0.0415; - b = X * 0.0557 + Y * -0.2040 + Z * 1.0570; - } - // clamp color values to [0,1] range then convert to [0,255] range. - dest[destOffset] = r <= 0 ? 0 : r >= 1 ? 255 : Math.sqrt(r) * 255 | 0; - dest[destOffset + 1] = g <= 0 ? 0 : g >= 1 ? 255 : Math.sqrt(g) * 255 | 0; - dest[destOffset + 2] = b <= 0 ? 0 : b >= 1 ? 255 : Math.sqrt(b) * 255 | 0; - } - - LabCS.prototype = { - getRgb: ColorSpace.prototype.getRgb, - getRgbItem: function LabCS_getRgbItem(src, srcOffset, dest, destOffset) { - convertToRgb(this, src, srcOffset, false, dest, destOffset); - }, - getRgbBuffer: function LabCS_getRgbBuffer(src, srcOffset, count, - dest, destOffset, bits, - alpha01) { - var maxVal = (1 << bits) - 1; - for (var i = 0; i < count; i++) { - convertToRgb(this, src, srcOffset, maxVal, dest, destOffset); - srcOffset += 3; - destOffset += 3 + alpha01; - } - }, - getOutputLength: function LabCS_getOutputLength(inputLength, alpha01) { - return (inputLength * (3 + alpha01) / 3) | 0; - }, - isPassthrough: ColorSpace.prototype.isPassthrough, - isDefaultDecode: function LabCS_isDefaultDecode(decodeMap) { - // XXX: Decoding is handled with the lab conversion because of the strange - // ranges that are used. - return true; - }, - usesZeroToOneRange: false - }; - return LabCS; -})(); - - - -var PDFFunction = (function PDFFunctionClosure() { - var CONSTRUCT_SAMPLED = 0; - var CONSTRUCT_INTERPOLATED = 2; - var CONSTRUCT_STICHED = 3; - var CONSTRUCT_POSTSCRIPT = 4; - - return { - getSampleArray: function PDFFunction_getSampleArray(size, outputSize, bps, - str) { - var length = 1; - for (var i = 0, ii = size.length; i < ii; i++) { - length *= size[i]; - } - length *= outputSize; - - var array = []; - var codeSize = 0; - var codeBuf = 0; - // 32 is a valid bps so shifting won't work - var sampleMul = 1.0 / (Math.pow(2.0, bps) - 1); - - var strBytes = str.getBytes((length * bps + 7) / 8); - var strIdx = 0; - for (var i = 0; i < length; i++) { - while (codeSize < bps) { - codeBuf <<= 8; - codeBuf |= strBytes[strIdx++]; - codeSize += 8; - } - codeSize -= bps; - array.push((codeBuf >> codeSize) * sampleMul); - codeBuf &= (1 << codeSize) - 1; - } - return array; - }, - - getIR: function PDFFunction_getIR(xref, fn) { - var dict = fn.dict; - if (!dict) { - dict = fn; - } - - var types = [this.constructSampled, - null, - this.constructInterpolated, - this.constructStiched, - this.constructPostScript]; - - var typeNum = dict.get('FunctionType'); - var typeFn = types[typeNum]; - if (!typeFn) { - error('Unknown type of function'); - } - - return typeFn.call(this, fn, dict, xref); - }, - - fromIR: function PDFFunction_fromIR(IR) { - var type = IR[0]; - switch (type) { - case CONSTRUCT_SAMPLED: - return this.constructSampledFromIR(IR); - case CONSTRUCT_INTERPOLATED: - return this.constructInterpolatedFromIR(IR); - case CONSTRUCT_STICHED: - return this.constructStichedFromIR(IR); - //case CONSTRUCT_POSTSCRIPT: - default: - return this.constructPostScriptFromIR(IR); - } - }, - - parse: function PDFFunction_parse(xref, fn) { - var IR = this.getIR(xref, fn); - return this.fromIR(IR); - }, - - constructSampled: function PDFFunction_constructSampled(str, dict) { - function toMultiArray(arr) { - var inputLength = arr.length; - var outputLength = arr.length / 2; - var out = []; - var index = 0; - for (var i = 0; i < inputLength; i += 2) { - out[index] = [arr[i], arr[i + 1]]; - ++index; - } - return out; - } - var domain = dict.get('Domain'); - var range = dict.get('Range'); - - if (!domain || !range) { - error('No domain or range'); - } - - var inputSize = domain.length / 2; - var outputSize = range.length / 2; - - domain = toMultiArray(domain); - range = toMultiArray(range); - - var size = dict.get('Size'); - var bps = dict.get('BitsPerSample'); - var order = dict.get('Order') || 1; - if (order !== 1) { - // No description how cubic spline interpolation works in PDF32000:2008 - // As in poppler, ignoring order, linear interpolation may work as good - info('No support for cubic spline interpolation: ' + order); - } - - var encode = dict.get('Encode'); - if (!encode) { - encode = []; - for (var i = 0; i < inputSize; ++i) { - encode.push(0); - encode.push(size[i] - 1); - } - } - encode = toMultiArray(encode); - - var decode = dict.get('Decode'); - if (!decode) { - decode = range; - } else { - decode = toMultiArray(decode); - } - - var samples = this.getSampleArray(size, outputSize, bps, str); - - return [ - CONSTRUCT_SAMPLED, inputSize, domain, encode, decode, samples, size, - outputSize, Math.pow(2, bps) - 1, range - ]; - }, - - constructSampledFromIR: function PDFFunction_constructSampledFromIR(IR) { - // See chapter 3, page 109 of the PDF reference - function interpolate(x, xmin, xmax, ymin, ymax) { - return ymin + ((x - xmin) * ((ymax - ymin) / (xmax - xmin))); - } - - return function constructSampledFromIRResult(args) { - // See chapter 3, page 110 of the PDF reference. - var m = IR[1]; - var domain = IR[2]; - var encode = IR[3]; - var decode = IR[4]; - var samples = IR[5]; - var size = IR[6]; - var n = IR[7]; - var mask = IR[8]; - var range = IR[9]; - - if (m != args.length) { - error('Incorrect number of arguments: ' + m + ' != ' + - args.length); - } - - var x = args; - - // Building the cube vertices: its part and sample index - // http://rjwagner49.com/Mathematics/Interpolation.pdf - var cubeVertices = 1 << m; - var cubeN = new Float64Array(cubeVertices); - var cubeVertex = new Uint32Array(cubeVertices); - for (var j = 0; j < cubeVertices; j++) { - cubeN[j] = 1; - } - - var k = n, pos = 1; - // Map x_i to y_j for 0 <= i < m using the sampled function. - for (var i = 0; i < m; ++i) { - // x_i' = min(max(x_i, Domain_2i), Domain_2i+1) - var domain_2i = domain[i][0]; - var domain_2i_1 = domain[i][1]; - var xi = Math.min(Math.max(x[i], domain_2i), domain_2i_1); - - // e_i = Interpolate(x_i', Domain_2i, Domain_2i+1, - // Encode_2i, Encode_2i+1) - var e = interpolate(xi, domain_2i, domain_2i_1, - encode[i][0], encode[i][1]); - - // e_i' = min(max(e_i, 0), Size_i - 1) - var size_i = size[i]; - e = Math.min(Math.max(e, 0), size_i - 1); - - // Adjusting the cube: N and vertex sample index - var e0 = e < size_i - 1 ? Math.floor(e) : e - 1; // e1 = e0 + 1; - var n0 = e0 + 1 - e; // (e1 - e) / (e1 - e0); - var n1 = e - e0; // (e - e0) / (e1 - e0); - var offset0 = e0 * k; - var offset1 = offset0 + k; // e1 * k - for (var j = 0; j < cubeVertices; j++) { - if (j & pos) { - cubeN[j] *= n1; - cubeVertex[j] += offset1; - } else { - cubeN[j] *= n0; - cubeVertex[j] += offset0; - } - } - - k *= size_i; - pos <<= 1; - } - - var y = new Float64Array(n); - for (var j = 0; j < n; ++j) { - // Sum all cube vertices' samples portions - var rj = 0; - for (var i = 0; i < cubeVertices; i++) { - rj += samples[cubeVertex[i] + j] * cubeN[i]; - } - - // r_j' = Interpolate(r_j, 0, 2^BitsPerSample - 1, - // Decode_2j, Decode_2j+1) - rj = interpolate(rj, 0, 1, decode[j][0], decode[j][1]); - - // y_j = min(max(r_j, range_2j), range_2j+1) - y[j] = Math.min(Math.max(rj, range[j][0]), range[j][1]); - } - - return y; - }; - }, - - constructInterpolated: function PDFFunction_constructInterpolated(str, - dict) { - var c0 = dict.get('C0') || [0]; - var c1 = dict.get('C1') || [1]; - var n = dict.get('N'); - - if (!isArray(c0) || !isArray(c1)) { - error('Illegal dictionary for interpolated function'); - } - - var length = c0.length; - var diff = []; - for (var i = 0; i < length; ++i) { - diff.push(c1[i] - c0[i]); - } - - return [CONSTRUCT_INTERPOLATED, c0, diff, n]; - }, - - constructInterpolatedFromIR: - function PDFFunction_constructInterpolatedFromIR(IR) { - var c0 = IR[1]; - var diff = IR[2]; - var n = IR[3]; - - var length = diff.length; - - return function constructInterpolatedFromIRResult(args) { - var x = n == 1 ? args[0] : Math.pow(args[0], n); - - var out = []; - for (var j = 0; j < length; ++j) { - out.push(c0[j] + (x * diff[j])); - } - - return out; - - }; - }, - - constructStiched: function PDFFunction_constructStiched(fn, dict, xref) { - var domain = dict.get('Domain'); - - if (!domain) { - error('No domain'); - } - - var inputSize = domain.length / 2; - if (inputSize != 1) { - error('Bad domain for stiched function'); - } - - var fnRefs = dict.get('Functions'); - var fns = []; - for (var i = 0, ii = fnRefs.length; i < ii; ++i) { - fns.push(PDFFunction.getIR(xref, xref.fetchIfRef(fnRefs[i]))); - } - - var bounds = dict.get('Bounds'); - var encode = dict.get('Encode'); - - return [CONSTRUCT_STICHED, domain, bounds, encode, fns]; - }, - - constructStichedFromIR: function PDFFunction_constructStichedFromIR(IR) { - var domain = IR[1]; - var bounds = IR[2]; - var encode = IR[3]; - var fnsIR = IR[4]; - var fns = []; - - for (var i = 0, ii = fnsIR.length; i < ii; i++) { - fns.push(PDFFunction.fromIR(fnsIR[i])); - } - - return function constructStichedFromIRResult(args) { - var clip = function constructStichedFromIRClip(v, min, max) { - if (v > max) { - v = max; - } else if (v < min) { - v = min; - } - return v; - }; - - // clip to domain - var v = clip(args[0], domain[0], domain[1]); - // calulate which bound the value is in - for (var i = 0, ii = bounds.length; i < ii; ++i) { - if (v < bounds[i]) { - break; - } - } - - // encode value into domain of function - var dmin = domain[0]; - if (i > 0) { - dmin = bounds[i - 1]; - } - var dmax = domain[1]; - if (i < bounds.length) { - dmax = bounds[i]; - } - - var rmin = encode[2 * i]; - var rmax = encode[2 * i + 1]; - - var v2 = rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin); - - // call the appropriate function - return fns[i]([v2]); - }; - }, - - constructPostScript: function PDFFunction_constructPostScript(fn, dict, - xref) { - var domain = dict.get('Domain'); - var range = dict.get('Range'); - - if (!domain) { - error('No domain.'); - } - - if (!range) { - error('No range.'); - } - - var lexer = new PostScriptLexer(fn); - var parser = new PostScriptParser(lexer); - var code = parser.parse(); - - return [CONSTRUCT_POSTSCRIPT, domain, range, code]; - }, - - constructPostScriptFromIR: function PDFFunction_constructPostScriptFromIR( - IR) { - var domain = IR[1]; - var range = IR[2]; - var code = IR[3]; - var numOutputs = range.length / 2; - var evaluator = new PostScriptEvaluator(code); - // Cache the values for a big speed up, the cache size is limited though - // since the number of possible values can be huge from a PS function. - var cache = new FunctionCache(); - return function constructPostScriptFromIRResult(args) { - var initialStack = []; - for (var i = 0, ii = (domain.length / 2); i < ii; ++i) { - initialStack.push(args[i]); - } - - var key = initialStack.join('_'); - if (cache.has(key)) { - return cache.get(key); - } - - var stack = evaluator.execute(initialStack); - var transformed = []; - for (i = numOutputs - 1; i >= 0; --i) { - var out = stack.pop(); - var rangeIndex = 2 * i; - if (out < range[rangeIndex]) { - out = range[rangeIndex]; - } else if (out > range[rangeIndex + 1]) { - out = range[rangeIndex + 1]; - } - transformed[i] = out; - } - cache.set(key, transformed); - return transformed; - }; - } - }; -})(); - -var FunctionCache = (function FunctionCacheClosure() { - // Of 10 PDF's with type4 functions the maxium number of distinct values seen - // was 256. This still may need some tweaking in the future though. - var MAX_CACHE_SIZE = 1024; - function FunctionCache() { - this.cache = {}; - this.total = 0; - } - FunctionCache.prototype = { - has: function FunctionCache_has(key) { - return key in this.cache; - }, - get: function FunctionCache_get(key) { - return this.cache[key]; - }, - set: function FunctionCache_set(key, value) { - if (this.total < MAX_CACHE_SIZE) { - this.cache[key] = value; - this.total++; - } - } - }; - return FunctionCache; -})(); - -var PostScriptStack = (function PostScriptStackClosure() { - var MAX_STACK_SIZE = 100; - function PostScriptStack(initialStack) { - this.stack = initialStack || []; - } - - PostScriptStack.prototype = { - push: function PostScriptStack_push(value) { - if (this.stack.length >= MAX_STACK_SIZE) { - error('PostScript function stack overflow.'); - } - this.stack.push(value); - }, - pop: function PostScriptStack_pop() { - if (this.stack.length <= 0) { - error('PostScript function stack underflow.'); - } - return this.stack.pop(); - }, - copy: function PostScriptStack_copy(n) { - if (this.stack.length + n >= MAX_STACK_SIZE) { - error('PostScript function stack overflow.'); - } - var stack = this.stack; - for (var i = stack.length - n, j = n - 1; j >= 0; j--, i++) { - stack.push(stack[i]); - } - }, - index: function PostScriptStack_index(n) { - this.push(this.stack[this.stack.length - n - 1]); - }, - // rotate the last n stack elements p times - roll: function PostScriptStack_roll(n, p) { - var stack = this.stack; - var l = stack.length - n; - var r = stack.length - 1, c = l + (p - Math.floor(p / n) * n), i, j, t; - for (i = l, j = r; i < j; i++, j--) { - t = stack[i]; stack[i] = stack[j]; stack[j] = t; - } - for (i = l, j = c - 1; i < j; i++, j--) { - t = stack[i]; stack[i] = stack[j]; stack[j] = t; - } - for (i = c, j = r; i < j; i++, j--) { - t = stack[i]; stack[i] = stack[j]; stack[j] = t; - } - } - }; - return PostScriptStack; -})(); -var PostScriptEvaluator = (function PostScriptEvaluatorClosure() { - function PostScriptEvaluator(operators) { - this.operators = operators; - } - PostScriptEvaluator.prototype = { - execute: function PostScriptEvaluator_execute(initialStack) { - var stack = new PostScriptStack(initialStack); - var counter = 0; - var operators = this.operators; - var length = operators.length; - var operator, a, b; - while (counter < length) { - operator = operators[counter++]; - if (typeof operator == 'number') { - // Operator is really an operand and should be pushed to the stack. - stack.push(operator); - continue; - } - switch (operator) { - // non standard ps operators - case 'jz': // jump if false - b = stack.pop(); - a = stack.pop(); - if (!a) { - counter = b; - } - break; - case 'j': // jump - a = stack.pop(); - counter = a; - break; - - // all ps operators in alphabetical order (excluding if/ifelse) - case 'abs': - a = stack.pop(); - stack.push(Math.abs(a)); - break; - case 'add': - b = stack.pop(); - a = stack.pop(); - stack.push(a + b); - break; - case 'and': - b = stack.pop(); - a = stack.pop(); - if (isBool(a) && isBool(b)) { - stack.push(a && b); - } else { - stack.push(a & b); - } - break; - case 'atan': - a = stack.pop(); - stack.push(Math.atan(a)); - break; - case 'bitshift': - b = stack.pop(); - a = stack.pop(); - if (a > 0) { - stack.push(a << b); - } else { - stack.push(a >> b); - } - break; - case 'ceiling': - a = stack.pop(); - stack.push(Math.ceil(a)); - break; - case 'copy': - a = stack.pop(); - stack.copy(a); - break; - case 'cos': - a = stack.pop(); - stack.push(Math.cos(a)); - break; - case 'cvi': - a = stack.pop() | 0; - stack.push(a); - break; - case 'cvr': - // noop - break; - case 'div': - b = stack.pop(); - a = stack.pop(); - stack.push(a / b); - break; - case 'dup': - stack.copy(1); - break; - case 'eq': - b = stack.pop(); - a = stack.pop(); - stack.push(a == b); - break; - case 'exch': - stack.roll(2, 1); - break; - case 'exp': - b = stack.pop(); - a = stack.pop(); - stack.push(Math.pow(a, b)); - break; - case 'false': - stack.push(false); - break; - case 'floor': - a = stack.pop(); - stack.push(Math.floor(a)); - break; - case 'ge': - b = stack.pop(); - a = stack.pop(); - stack.push(a >= b); - break; - case 'gt': - b = stack.pop(); - a = stack.pop(); - stack.push(a > b); - break; - case 'idiv': - b = stack.pop(); - a = stack.pop(); - stack.push((a / b) | 0); - break; - case 'index': - a = stack.pop(); - stack.index(a); - break; - case 'le': - b = stack.pop(); - a = stack.pop(); - stack.push(a <= b); - break; - case 'ln': - a = stack.pop(); - stack.push(Math.log(a)); - break; - case 'log': - a = stack.pop(); - stack.push(Math.log(a) / Math.LN10); - break; - case 'lt': - b = stack.pop(); - a = stack.pop(); - stack.push(a < b); - break; - case 'mod': - b = stack.pop(); - a = stack.pop(); - stack.push(a % b); - break; - case 'mul': - b = stack.pop(); - a = stack.pop(); - stack.push(a * b); - break; - case 'ne': - b = stack.pop(); - a = stack.pop(); - stack.push(a != b); - break; - case 'neg': - a = stack.pop(); - stack.push(-b); - break; - case 'not': - a = stack.pop(); - if (isBool(a) && isBool(b)) { - stack.push(a && b); - } else { - stack.push(a & b); - } - break; - case 'or': - b = stack.pop(); - a = stack.pop(); - if (isBool(a) && isBool(b)) { - stack.push(a || b); - } else { - stack.push(a | b); - } - break; - case 'pop': - stack.pop(); - break; - case 'roll': - b = stack.pop(); - a = stack.pop(); - stack.roll(a, b); - break; - case 'round': - a = stack.pop(); - stack.push(Math.round(a)); - break; - case 'sin': - a = stack.pop(); - stack.push(Math.sin(a)); - break; - case 'sqrt': - a = stack.pop(); - stack.push(Math.sqrt(a)); - break; - case 'sub': - b = stack.pop(); - a = stack.pop(); - stack.push(a - b); - break; - case 'true': - stack.push(true); - break; - case 'truncate': - a = stack.pop(); - a = a < 0 ? Math.ceil(a) : Math.floor(a); - stack.push(a); - break; - case 'xor': - b = stack.pop(); - a = stack.pop(); - if (isBool(a) && isBool(b)) { - stack.push(a != b); - } else { - stack.push(a ^ b); - } - break; - default: - error('Unknown operator ' + operator); - break; - } - } - return stack.stack; - } - }; - return PostScriptEvaluator; -})(); - - +var DEFAULT_ICON_SIZE = 22; // px var HIGHLIGHT_OFFSET = 4; // px var SUPPORTED_TYPES = ['Link', 'Text', 'Widget']; @@ -3034,7 +1618,7 @@ var Annotation = (function AnnotationClosure() { var data = this.data = {}; data.subtype = dict.get('Subtype').name; - var rect = dict.get('Rect'); + var rect = dict.get('Rect') || [0, 0, 0, 0]; data.rect = Util.normalizeRect(rect); data.annotationFlags = dict.get('F'); @@ -3058,23 +1642,30 @@ var Annotation = (function AnnotationClosure() { // TODO: implement proper support for annotations with line dash patterns. var dashArray = borderArray[3]; - if (data.borderWidth > 0 && dashArray && isArray(dashArray)) { - var dashArrayLength = dashArray.length; - if (dashArrayLength > 0) { - // According to the PDF specification: the elements in a dashArray - // shall be numbers that are nonnegative and not all equal to zero. - var isInvalid = false; - var numPositive = 0; - for (var i = 0; i < dashArrayLength; i++) { - if (!(+dashArray[i] >= 0)) { - isInvalid = true; - break; - } else if (dashArray[i] > 0) { - numPositive++; + if (data.borderWidth > 0 && dashArray) { + if (!isArray(dashArray)) { + // Ignore the border if dashArray is not actually an array, + // this is consistent with the behaviour in Adobe Reader. + data.borderWidth = 0; + } else { + var dashArrayLength = dashArray.length; + if (dashArrayLength > 0) { + // According to the PDF specification: the elements in a dashArray + // shall be numbers that are nonnegative and not all equal to zero. + var isInvalid = false; + var numPositive = 0; + for (var i = 0; i < dashArrayLength; i++) { + var validNumber = (+dashArray[i] >= 0); + if (!validNumber) { + isInvalid = true; + break; + } else if (dashArray[i] > 0) { + numPositive++; + } + } + if (isInvalid || numPositive === 0) { + data.borderWidth = 0; } - } - if (isInvalid || numPositive === 0) { - data.borderWidth = 0; } } } @@ -3082,6 +1673,7 @@ var Annotation = (function AnnotationClosure() { this.appearance = getDefaultAppearance(dict); data.hasAppearance = !!this.appearance; + data.id = params.ref.num; } Annotation.prototype = { @@ -3146,31 +1738,27 @@ var Annotation = (function AnnotationClosure() { data.rect); // rectangle is nessessary }, - loadResources: function(keys) { - var promise = new LegacyPromise(); - this.appearance.dict.getAsync('Resources').then(function(resources) { - if (!resources) { - promise.resolve(); - return; - } - var objectLoader = new ObjectLoader(resources.map, - keys, - resources.xref); - objectLoader.load().then(function() { - promise.resolve(resources); - }); + loadResources: function Annotation_loadResources(keys) { + return new Promise(function (resolve, reject) { + this.appearance.dict.getAsync('Resources').then(function (resources) { + if (!resources) { + resolve(); + return; + } + var objectLoader = new ObjectLoader(resources.map, + keys, + resources.xref); + objectLoader.load().then(function() { + resolve(resources); + }, reject); + }, reject); }.bind(this)); - - return promise; }, getOperatorList: function Annotation_getOperatorList(evaluator) { - var promise = new LegacyPromise(); - if (!this.appearance) { - promise.resolve(new OperatorList()); - return promise; + return Promise.resolve(new OperatorList()); } var data = this.data; @@ -3189,20 +1777,18 @@ var Annotation = (function AnnotationClosure() { var bbox = appearanceDict.get('BBox') || [0, 0, 1, 1]; var matrix = appearanceDict.get('Matrix') || [1, 0, 0, 1, 0 ,0]; var transform = getTransformMatrix(data.rect, bbox, matrix); + var self = this; - var border = data.border; - - resourcesPromise.then(function(resources) { - var opList = new OperatorList(); - opList.addOp(OPS.beginAnnotation, [data.rect, transform, matrix]); - evaluator.getOperatorList(this.appearance, resources, opList); - opList.addOp(OPS.endAnnotation, []); - promise.resolve(opList); - - this.appearance.reset(); - }.bind(this)); - - return promise; + return resourcesPromise.then(function(resources) { + var opList = new OperatorList(); + opList.addOp(OPS.beginAnnotation, [data.rect, transform, matrix]); + return evaluator.getOperatorList(self.appearance, resources, opList). + then(function () { + opList.addOp(OPS.endAnnotation, []); + self.appearance.reset(); + return opList; + }); + }); } }; @@ -3282,10 +1868,10 @@ var Annotation = (function AnnotationClosure() { annotations, opList, pdfManager, partialEvaluator, intent) { function reject(e) { - annotationsReadyPromise.reject(e); + annotationsReadyCapability.reject(e); } - var annotationsReadyPromise = new LegacyPromise(); + var annotationsReadyCapability = createPromiseCapability(); var annotationPromises = []; for (var i = 0, n = annotations.length; i < n; ++i) { @@ -3302,10 +1888,10 @@ var Annotation = (function AnnotationClosure() { opList.addOpList(annotOpList); } opList.addOp(OPS.endAnnotations, []); - annotationsReadyPromise.resolve(); + annotationsReadyCapability.resolve(); }, reject); - return annotationsReadyPromise; + return annotationsReadyCapability.promise; }; return Annotation; @@ -3332,7 +1918,7 @@ var WidgetAnnotation = (function WidgetAnnotationClosure() { var fieldType = Util.getInheritableProperty(dict, 'FT'); data.fieldType = isName(fieldType) ? fieldType.name : ''; data.fieldFlags = Util.getInheritableProperty(dict, 'Ff') || 0; - this.fieldResources = Util.getInheritableProperty(dict, 'DR') || new Dict(); + this.fieldResources = Util.getInheritableProperty(dict, 'DR') || Dict.empty; // Building the full field name by collecting the field and // its ancestors 'T' data and joining them using '.'. @@ -3417,7 +2003,6 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() { } - var parent = WidgetAnnotation.prototype; Util.inherit(TextWidgetAnnotation, WidgetAnnotation, { hasHtml: function TextWidgetAnnotation_hasHtml() { return !this.data.hasAppearance && !!this.data.fieldValue; @@ -3440,7 +2025,7 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() { var fontObj = item.fontRefName ? commonObjs.getData(item.fontRefName) : null; - var cssRules = setTextStyles(content, item, fontObj); + setTextStyles(content, item, fontObj); element.appendChild(content); @@ -3452,54 +2037,20 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() { return Annotation.prototype.getOperatorList.call(this, evaluator); } - var promise = new LegacyPromise(); var opList = new OperatorList(); var data = this.data; // Even if there is an appearance stream, ignore it. This is the // behaviour used by Adobe Reader. - - var defaultAppearance = data.defaultAppearance; - if (!defaultAppearance) { - promise.resolve(opList); - return promise; + if (!data.defaultAppearance) { + return Promise.resolve(opList); } - // Include any font resources found in the default appearance - - var stream = new Stream(stringToBytes(defaultAppearance)); - evaluator.getOperatorList(stream, this.fieldResources, opList); - var appearanceFnArray = opList.fnArray; - var appearanceArgsArray = opList.argsArray; - var fnArray = []; - var argsArray = []; - - // TODO(mack): Add support for stroke color - data.rgb = [0, 0, 0]; - // TODO THIS DOESN'T MAKE ANY SENSE SINCE THE fnArray IS EMPTY! - for (var i = 0, n = fnArray.length; i < n; ++i) { - var fnId = appearanceFnArray[i]; - var args = appearanceArgsArray[i]; - - if (fnId === OPS.setFont) { - data.fontRefName = args[0]; - var size = args[1]; - if (size < 0) { - data.fontDirection = -1; - data.fontSize = -size; - } else { - data.fontDirection = 1; - data.fontSize = size; - } - } else if (fnId === OPS.setFillRGBColor) { - data.rgb = args; - } else if (fnId === OPS.setFillGray) { - var rgbValue = args[0] * 255; - data.rgb = [rgbValue, rgbValue, rgbValue]; - } - } - promise.resolve(opList); - return promise; + var stream = new Stream(stringToBytes(data.defaultAppearance)); + return evaluator.getOperatorList(stream, this.fieldResources, opList). + then(function () { + return opList; + }); } }); @@ -3580,6 +2131,8 @@ var TextAnnotation = (function TextAnnotationClosure() { if (data.hasAppearance) { data.name = 'NoIcon'; } else { + data.rect[1] = data.rect[3] - DEFAULT_ICON_SIZE; + data.rect[2] = data.rect[0] + DEFAULT_ICON_SIZE; data.name = dict.has('Name') ? dict.get('Name').name : 'Note'; } @@ -3627,10 +2180,12 @@ var TextAnnotation = (function TextAnnotationClosure() { var content = document.createElement('div'); content.className = 'annotTextContent'; content.setAttribute('hidden', true); + + var i, ii; if (item.hasBgColor) { var color = item.color; var rgb = []; - for (var i = 0; i < 3; ++i) { + for (i = 0; i < 3; ++i) { // Enlighten the color (70%) var c = Math.round(color[i] * 255); rgb[i] = Math.round((255 - c) * 0.7) + c; @@ -3647,7 +2202,7 @@ var TextAnnotation = (function TextAnnotationClosure() { } else { var e = document.createElement('span'); var lines = item.content.split(/(?:\r\n?|\n)/); - for (var i = 0, ii = lines.length; i < ii; ++i) { + for (i = 0, ii = lines.length; i < ii; ++i) { var line = lines[i]; e.appendChild(document.createTextNode(line)); if (i < (ii - 1)) { @@ -3686,7 +2241,6 @@ var TextAnnotation = (function TextAnnotationClosure() { } }; - var self = this; image.addEventListener('click', function image_clickHandler() { toggleAnnotation(); }, false); @@ -3791,7 +2345,6 @@ var LinkAnnotation = (function LinkAnnotationClosure() { container.className = 'annotLink'; var item = this.data; - var rect = item.rect; container.style.borderColor = item.colorCssRgb; container.style.borderStyle = 'solid'; @@ -3824,6 +2377,12 @@ PDFJS.maxImageSize = (PDFJS.maxImageSize === undefined ? */ PDFJS.cMapUrl = (PDFJS.cMapUrl === undefined ? null : PDFJS.cMapUrl); +/** + * Specifies if CMaps are binary packed. + * @var {boolean} + */ +PDFJS.cMapPacked = PDFJS.cMapPacked === undefined ? false : PDFJS.cMapPacked; + /* * By default fonts are converted to OpenType fonts and loaded via font face * rules. If disabled, the font will be rendered using a built in font renderer @@ -3897,6 +2456,13 @@ PDFJS.disableCreateObjectURL = (PDFJS.disableCreateObjectURL === undefined ? false : PDFJS.disableCreateObjectURL); /** + * Disables WebGL usage. + * @var {boolean} + */ +PDFJS.disableWebGL = (PDFJS.disableWebGL === undefined ? + true : PDFJS.disableWebGL); + +/** * Controls the logging level. * The constants from PDFJS.VERBOSITY_LEVELS should be used: * - errors @@ -3949,7 +2515,7 @@ PDFJS.getDocument = function getDocument(source, pdfDataRangeTransport, passwordCallback, progressCallback) { - var workerInitializedPromise, workerReadyPromise, transport; + var workerInitializedCapability, workerReadyCapability, transport; if (typeof source === 'string') { source = { url: source }; @@ -3974,15 +2540,16 @@ PDFJS.getDocument = function getDocument(source, params[key] = source[key]; } - workerInitializedPromise = new PDFJS.LegacyPromise(); - workerReadyPromise = new PDFJS.LegacyPromise(); - transport = new WorkerTransport(workerInitializedPromise, workerReadyPromise, - pdfDataRangeTransport, progressCallback); - workerInitializedPromise.then(function transportInitialized() { + workerInitializedCapability = createPromiseCapability(); + workerReadyCapability = createPromiseCapability(); + transport = new WorkerTransport(workerInitializedCapability, + workerReadyCapability, pdfDataRangeTransport, + progressCallback); + workerInitializedCapability.promise.then(function transportInitialized() { transport.passwordCallback = passwordCallback; transport.fetchDocument(params); }); - return workerReadyPromise; + return workerReadyCapability.promise; }; /** @@ -4034,14 +2601,18 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() { return this.transport.getDestinations(); }, /** + * @return {Promise} A promise that is resolved with a lookup table for + * mapping named attachments to their content. + */ + getAttachments: function PDFDocumentProxy_getAttachments() { + return this.transport.getAttachments(); + }, + /** * @return {Promise} A promise that is resolved with an array of all the * JavaScript strings in the name tree. */ getJavaScript: function PDFDocumentProxy_getJavaScript() { - var promise = new PDFJS.LegacyPromise(); - var js = this.pdfInfo.javaScript; - promise.resolve(js); - return promise; + return this.transport.getJavaScript(); }, /** * @return {Promise} A promise that is resolved with an {Array} that is a @@ -4059,10 +2630,7 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() { * ]. */ getOutline: function PDFDocumentProxy_getOutline() { - var promise = new PDFJS.LegacyPromise(); - var outline = this.pdfInfo.outline; - promise.resolve(outline); - return promise; + return this.transport.getOutline(); }, /** * @return {Promise} A promise that is resolved with an {Object} that has @@ -4071,23 +2639,14 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() { * {Metadata} object with information from the metadata section of the PDF. */ getMetadata: function PDFDocumentProxy_getMetadata() { - var promise = new PDFJS.LegacyPromise(); - var info = this.pdfInfo.info; - var metadata = this.pdfInfo.metadata; - promise.resolve({ - info: info, - metadata: (metadata ? new PDFJS.Metadata(metadata) : null) - }); - return promise; + return this.transport.getMetadata(); }, /** * @return {Promise} A promise that is resolved with a TypedArray that has * the raw data from the PDF. */ getData: function PDFDocumentProxy_getData() { - var promise = new PDFJS.LegacyPromise(); - this.transport.getData(promise); - return promise; + return this.transport.getData(); }, /** * @return {Promise} A promise that is resolved when the document's data @@ -4095,7 +2654,7 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() { * property that indicates size of the PDF data in bytes. */ getDownloadInfo: function PDFDocumentProxy_getDownloadInfo() { - return this.transport.downloadInfoPromise; + return this.transport.downloadInfoCapability.promise; }, /** * Cleans up resources allocated by the document, e.g. created @font-face. @@ -4114,15 +2673,51 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() { })(); /** + * Page text content. + * + * @typedef {Object} TextContent + * @property {array} items - array of {@link TextItem} + * @property {Object} styles - {@link TextStyles} objects, indexed by font + * name. + */ + +/** * Page text content part. * - * @typedef {Object} BidiText + * @typedef {Object} TextItem * @property {string} str - text content. * @property {string} dir - text direction: 'ttb', 'ltr' or 'rtl'. - * @property {number} x - x position of the text on the page. - * @property {number} y - y position of the text on the page. - * @property {number} angle - text rotation. - * @property {number} size - font size. + * @property {array} transform - transformation matrix. + * @property {number} width - width in device space. + * @property {number} height - height in device space. + * @property {string} fontName - font name used by pdf.js for converted font. + */ + +/** + * Text style. + * + * @typedef {Object} TextStyle + * @property {number} ascent - font ascent. + * @property {number} descent - font descent. + * @property {boolean} vertical - text is in vertical mode. + * @property {string} fontFamily - possible font family + */ + +/** + * Page render parameters. + * + * @typedef {Object} RenderParameters + * @property {Object} canvasContext - A 2D context of a DOM Canvas object. + * @property {PDFJS.PageViewport} viewport - Rendering viewport obtained by + * calling of PDFPage.getViewport method. + * @property {string} intent - Rendering intent, can be 'display' or 'print' + * (default value is 'display'). + * @property {Object} imageLayer - (optional) An object that has beginLayout, + * endLayout and appendImage functions. + * @property {function} continueCallback - (optional) A function that will be + * called each time the rendering is paused. To continue + * rendering call the function that is the first argument + * to the callback. */ /** @@ -4130,7 +2725,8 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() { * @class */ var PDFPageProxy = (function PDFPageProxyClosure() { - function PDFPageProxy(pageInfo, transport) { + function PDFPageProxy(pageIndex, pageInfo, transport) { + this.pageIndex = pageIndex; this.pageInfo = pageInfo; this.transport = transport; this.stats = new StatTimer(); @@ -4146,7 +2742,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() { * @return {number} Page number of the page. First page is 1. */ get pageNumber() { - return this.pageInfo.pageIndex + 1; + return this.pageIndex + 1; }, /** * @return {number} The number of degrees the page is rotated clockwise. @@ -4172,8 +2768,8 @@ var PDFPageProxy = (function PDFPageProxyClosure() { * @param {number} scale The desired scale of the viewport. * @param {number} rotate Degrees to rotate the viewport. If omitted this * defaults to the page rotation. - * @return {PageViewport} Contains 'width' and 'height' properties along - * with transforms required for rendering. + * @return {PDFJS.PageViewport} Contains 'width' and 'height' properties + * along with transforms required for rendering. */ getViewport: function PDFPageProxy_getViewport(scale, rotate) { if (arguments.length < 2) { @@ -4190,27 +2786,15 @@ var PDFPageProxy = (function PDFPageProxyClosure() { return this.annotationsPromise; } - var promise = new PDFJS.LegacyPromise(); + var promise = this.transport.getAnnotations(this.pageIndex); this.annotationsPromise = promise; - this.transport.getAnnotations(this.pageInfo.pageIndex); return promise; }, /** * Begins the process of rendering a page to the desired context. - * @param {Object} params A parameter object that supports: - * { - * canvasContext(required): A 2D context of a DOM Canvas object., - * textLayer(optional): An object that has beginLayout, endLayout, and - * appendText functions., - * imageLayer(optional): An object that has beginLayout, endLayout and - * appendImage functions., - * continueCallback(optional): A function that will be called each time - * the rendering is paused. To continue - * rendering call the function that is the - * first argument to the callback. - * }. - * @return {RenderTask} An extended promise that is resolved when the page - * finishes rendering (see RenderTask). + * @param {RenderParameters} params Page render parameters. + * @return {RenderTask} An object that contains the promise, which + * is resolved when the page finishes rendering. */ render: function PDFPageProxy_render(params) { var stats = this.stats; @@ -4228,11 +2812,11 @@ var PDFPageProxy = (function PDFPageProxyClosure() { } var intentState = this.intentStates[renderingIntent]; - // If there is no displayReadyPromise yet, then the operatorList was never - // requested before. Make the request and create the promise. - if (!intentState.displayReadyPromise) { + // If there's no displayReadyCapability yet, then the operatorList + // was never requested before. Make the request and create the promise. + if (!intentState.displayReadyCapability) { intentState.receivingOperatorList = true; - intentState.displayReadyPromise = new LegacyPromise(); + intentState.displayReadyCapability = createPromiseCapability(); intentState.operatorList = { fnArray: [], argsArray: [], @@ -4258,7 +2842,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() { var renderTask = new RenderTask(internalRenderTask); var self = this; - intentState.displayReadyPromise.then( + intentState.displayReadyCapability.promise.then( function pageDisplayReadyPromise(transparency) { if (self.pendingDestroy) { complete(); @@ -4285,9 +2869,9 @@ var PDFPageProxy = (function PDFPageProxyClosure() { self._tryDestroy(); if (error) { - renderTask.promise.reject(error); + internalRenderTask.capability.reject(error); } else { - renderTask.promise.resolve(); + internalRenderTask.capability.resolve(); } stats.timeEnd('Rendering'); stats.timeEnd('Overall'); @@ -4296,19 +2880,13 @@ var PDFPageProxy = (function PDFPageProxyClosure() { return renderTask; }, /** - * @return {Promise} That is resolved with the array of {@link BidiText} - * objects that represent the page text content. + * @return {Promise} That is resolved a {@link TextContent} + * object that represent the page text content. */ getTextContent: function PDFPageProxy_getTextContent() { - var promise = new PDFJS.LegacyPromise(); - this.transport.messageHandler.send('GetTextContent', { - pageIndex: this.pageNumber - 1 - }, - function textContentCallback(textContent) { - promise.resolve(textContent); - } - ); - return promise; + return this.transport.messageHandler.sendWithPromise('GetTextContent', { + pageIndex: this.pageNumber - 1 + }); }, /** * Destroys resources allocated by the page. @@ -4336,6 +2914,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() { delete this.intentStates[intent]; }, this); this.objs.clear(); + this.annotationsPromise = null; this.pendingDestroy = false; }, /** @@ -4345,7 +2924,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() { _startRenderPage: function PDFPageProxy_startRenderPage(transparency, intent) { var intentState = this.intentStates[intent]; - intentState.displayReadyPromise.resolve(transparency); + intentState.displayReadyCapability.resolve(transparency); }, /** * For internal use only. @@ -4354,8 +2933,9 @@ var PDFPageProxy = (function PDFPageProxyClosure() { _renderPageChunk: function PDFPageProxy_renderPageChunk(operatorListChunk, intent) { var intentState = this.intentStates[intent]; + var i, ii; // Add the new chunk to the current operator list. - for (var i = 0, ii = operatorListChunk.length; i < ii; i++) { + for (i = 0, ii = operatorListChunk.length; i < ii; i++) { intentState.operatorList.fnArray.push(operatorListChunk.fnArray[i]); intentState.operatorList.argsArray.push( operatorListChunk.argsArray[i]); @@ -4363,7 +2943,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() { intentState.operatorList.lastChunk = operatorListChunk.lastChunk; // Notify all the rendering tasks there are more operators to be consumed. - for (var i = 0; i < intentState.renderTasks.length; i++) { + for (i = 0; i < intentState.renderTasks.length; i++) { intentState.renderTasks[i].operatorListChanged(); } @@ -4381,17 +2961,17 @@ var PDFPageProxy = (function PDFPageProxyClosure() { * @ignore */ var WorkerTransport = (function WorkerTransportClosure() { - function WorkerTransport(workerInitializedPromise, workerReadyPromise, + function WorkerTransport(workerInitializedCapability, workerReadyCapability, pdfDataRangeTransport, progressCallback) { this.pdfDataRangeTransport = pdfDataRangeTransport; - this.workerReadyPromise = workerReadyPromise; + this.workerReadyCapability = workerReadyCapability; this.progressCallback = progressCallback; this.commonObjs = new PDFObjects(); this.pageCache = []; this.pagePromises = []; - this.downloadInfoPromise = new PDFJS.LegacyPromise(); + this.downloadInfoCapability = createPromiseCapability(); this.passwordCallback = null; // If worker support isn't disabled explicit and the browser has worker @@ -4420,12 +3000,12 @@ var WorkerTransport = (function WorkerTransportClosure() { PDFJS.postMessageTransfers = false; } this.setupMessageHandler(messageHandler); - workerInitializedPromise.resolve(); + workerInitializedCapability.resolve(); } else { globalScope.PDFJS.disableWorker = true; this.loadFakeWorkerFiles().then(function() { this.setupFakeWorker(); - workerInitializedPromise.resolve(); + workerInitializedCapability.resolve(); }.bind(this)); } }.bind(this)); @@ -4434,7 +3014,7 @@ var WorkerTransport = (function WorkerTransportClosure() { // Some versions of Opera throw a DATA_CLONE_ERR on serializing the // typed array. Also, checking if we can use transfers. try { - messageHandler.send('test', testObj, null, [testObj.buffer]); + messageHandler.send('test', testObj, [testObj.buffer]); } catch (ex) { info('Cannot use postMessage transfers'); testObj[0] = 0; @@ -4450,7 +3030,7 @@ var WorkerTransport = (function WorkerTransportClosure() { globalScope.PDFJS.disableWorker = true; this.loadFakeWorkerFiles().then(function() { this.setupFakeWorker(); - workerInitializedPromise.resolve(); + workerInitializedCapability.resolve(); }.bind(this)); } WorkerTransport.prototype = { @@ -4458,7 +3038,7 @@ var WorkerTransport = (function WorkerTransportClosure() { this.pageCache = []; this.pagePromises = []; var self = this; - this.messageHandler.send('Terminate', null, function () { + this.messageHandler.sendWithPromise('Terminate', null).then(function () { FontLoader.clear(); if (self.worker) { self.worker.terminate(); @@ -4467,16 +3047,16 @@ var WorkerTransport = (function WorkerTransportClosure() { }, loadFakeWorkerFiles: function WorkerTransport_loadFakeWorkerFiles() { - if (!PDFJS.fakeWorkerFilesLoadedPromise) { - PDFJS.fakeWorkerFilesLoadedPromise = new LegacyPromise(); + if (!PDFJS.fakeWorkerFilesLoadedCapability) { + PDFJS.fakeWorkerFilesLoadedCapability = createPromiseCapability(); // In the developer build load worker_loader which in turn loads all the // other files and resolves the promise. In production only the // pdf.worker.js file is needed. Util.loadScript(PDFJS.workerSrc, function() { - PDFJS.fakeWorkerFilesLoadedPromise.resolve(); + PDFJS.fakeWorkerFilesLoadedCapability.resolve(); }); } - return PDFJS.fakeWorkerFilesLoadedPromise; + return PDFJS.fakeWorkerFilesLoadedCapability.promise; }, setupFakeWorker: function WorkerTransport_setupFakeWorker() { @@ -4531,7 +3111,7 @@ var WorkerTransport = (function WorkerTransportClosure() { this.numPages = data.pdfInfo.numPages; var pdfDocument = new PDFDocumentProxy(pdfInfo, this); this.pdfDocument = pdfDocument; - this.workerReadyPromise.resolve(pdfDocument); + this.workerReadyCapability.resolve(pdfDocument); }, this); messageHandler.on('NeedPassword', function transportPassword(data) { @@ -4539,7 +3119,8 @@ var WorkerTransport = (function WorkerTransportClosure() { return this.passwordCallback(updatePassword, PasswordResponses.NEED_PASSWORD); } - this.workerReadyPromise.reject(data.exception.message, data.exception); + this.workerReadyCapability.reject(data.exception.message, + data.exception); }, this); messageHandler.on('IncorrectPassword', function transportBadPass(data) { @@ -4547,37 +3128,26 @@ var WorkerTransport = (function WorkerTransportClosure() { return this.passwordCallback(updatePassword, PasswordResponses.INCORRECT_PASSWORD); } - this.workerReadyPromise.reject(data.exception.message, data.exception); + this.workerReadyCapability.reject(data.exception.message, + data.exception); }, this); messageHandler.on('InvalidPDF', function transportInvalidPDF(data) { - this.workerReadyPromise.reject(data.exception.name, data.exception); + this.workerReadyCapability.reject(data.exception.name, data.exception); }, this); messageHandler.on('MissingPDF', function transportMissingPDF(data) { - this.workerReadyPromise.reject(data.exception.message, data.exception); + this.workerReadyCapability.reject(data.exception.message, + data.exception); }, this); messageHandler.on('UnknownError', function transportUnknownError(data) { - this.workerReadyPromise.reject(data.exception.message, data.exception); + this.workerReadyCapability.reject(data.exception.message, + data.exception); }, this); messageHandler.on('DataLoaded', function transportPage(data) { - this.downloadInfoPromise.resolve(data); - }, this); - - messageHandler.on('GetPage', function transportPage(data) { - var pageInfo = data.pageInfo; - var page = new PDFPageProxy(pageInfo, this); - this.pageCache[pageInfo.pageIndex] = page; - var promise = this.pagePromises[pageInfo.pageIndex]; - promise.resolve(page); - }, this); - - messageHandler.on('GetAnnotations', function transportAnnotations(data) { - var annotations = data.annotations; - var promise = this.pageCache[data.pageIndex].annotationsPromise; - promise.resolve(annotations); + this.downloadInfoCapability.resolve(data); }, this); messageHandler.on('StartRenderPage', function transportRender(data) { @@ -4634,22 +3204,23 @@ var WorkerTransport = (function WorkerTransportClosure() { var pageIndex = data[1]; var type = data[2]; var pageProxy = this.pageCache[pageIndex]; + var imageData; if (pageProxy.objs.hasData(id)) { return; } switch (type) { case 'JpegStream': - var imageData = data[3]; + imageData = data[3]; loadJpegStream(id, imageData, pageProxy.objs); break; case 'Image': - var imageData = data[3]; + imageData = data[3]; pageProxy.objs.resolve(id, imageData); // heuristics that will allow not to store large data var MAX_IMAGE_SIZE_TO_STORE = 8000000; - if ('data' in imageData && + if (imageData && 'data' in imageData && imageData.data.length > MAX_IMAGE_SIZE_TO_STORE) { pageProxy.cleanupAfterRender = true; } @@ -4669,52 +3240,59 @@ var WorkerTransport = (function WorkerTransportClosure() { }, this); messageHandler.on('DocError', function transportDocError(data) { - this.workerReadyPromise.reject(data); + this.workerReadyCapability.reject(data); }, this); - messageHandler.on('PageError', function transportError(data, intent) { + messageHandler.on('PageError', function transportError(data) { var page = this.pageCache[data.pageNum - 1]; - var intentState = page.intentStates[intent]; - if (intentState.displayReadyPromise) { - intentState.displayReadyPromise.reject(data.error); + var intentState = page.intentStates[data.intent]; + if (intentState.displayReadyCapability.promise) { + intentState.displayReadyCapability.reject(data.error); } else { error(data.error); } }, this); - messageHandler.on('JpegDecode', function(data, deferred) { + messageHandler.on('JpegDecode', function(data) { var imageUrl = data[0]; var components = data[1]; if (components != 3 && components != 1) { - error('Only 3 component or 1 component can be returned'); + return Promise.reject( + new Error('Only 3 components or 1 component can be returned')); } - var img = new Image(); - img.onload = (function messageHandler_onloadClosure() { - var width = img.width; - var height = img.height; - var size = width * height; - var rgbaLength = size * 4; - var buf = new Uint8Array(size * components); - var tmpCanvas = createScratchCanvas(width, height); - var tmpCtx = tmpCanvas.getContext('2d'); - tmpCtx.drawImage(img, 0, 0); - var data = tmpCtx.getImageData(0, 0, width, height).data; + return new Promise(function (resolve, reject) { + var img = new Image(); + img.onload = function () { + var width = img.width; + var height = img.height; + var size = width * height; + var rgbaLength = size * 4; + var buf = new Uint8Array(size * components); + var tmpCanvas = createScratchCanvas(width, height); + var tmpCtx = tmpCanvas.getContext('2d'); + tmpCtx.drawImage(img, 0, 0); + var data = tmpCtx.getImageData(0, 0, width, height).data; + var i, j; - if (components == 3) { - for (var i = 0, j = 0; i < rgbaLength; i += 4, j += 3) { - buf[j] = data[i]; - buf[j + 1] = data[i + 1]; - buf[j + 2] = data[i + 2]; - } - } else if (components == 1) { - for (var i = 0, j = 0; i < rgbaLength; i += 4, j++) { - buf[j] = data[i]; + if (components == 3) { + for (i = 0, j = 0; i < rgbaLength; i += 4, j += 3) { + buf[j] = data[i]; + buf[j + 1] = data[i + 1]; + buf[j + 2] = data[i + 2]; + } + } else if (components == 1) { + for (i = 0, j = 0; i < rgbaLength; i += 4, j++) { + buf[j] = data[i]; + } } - } - deferred.resolve({ data: buf, width: width, height: height}); - }).bind(this); - img.src = imageUrl; + resolve({ data: buf, width: width, height: height}); + }; + img.onerror = function () { + reject(new Error('JpegDecode failed to load image')); + }; + img.src = imageUrl; + }); }); }, @@ -4726,74 +3304,85 @@ var WorkerTransport = (function WorkerTransportClosure() { disableRange: PDFJS.disableRange, maxImageSize: PDFJS.maxImageSize, cMapUrl: PDFJS.cMapUrl, + cMapPacked: PDFJS.cMapPacked, disableFontFace: PDFJS.disableFontFace, disableCreateObjectURL: PDFJS.disableCreateObjectURL, verbosity: PDFJS.verbosity }); }, - getData: function WorkerTransport_getData(promise) { - this.messageHandler.send('GetData', null, function(data) { - promise.resolve(data); - }); + getData: function WorkerTransport_getData() { + return this.messageHandler.sendWithPromise('GetData', null); }, - getPage: function WorkerTransport_getPage(pageNumber, promise) { + getPage: function WorkerTransport_getPage(pageNumber, capability) { if (pageNumber <= 0 || pageNumber > this.numPages || (pageNumber|0) !== pageNumber) { - var pagePromise = new PDFJS.LegacyPromise(); - pagePromise.reject(new Error('Invalid page request')); - return pagePromise; + return Promise.reject(new Error('Invalid page request')); } var pageIndex = pageNumber - 1; if (pageIndex in this.pagePromises) { return this.pagePromises[pageIndex]; } - var promise = new PDFJS.LegacyPromise(); + var promise = this.messageHandler.sendWithPromise('GetPage', { + pageIndex: pageIndex + }).then(function (pageInfo) { + var page = new PDFPageProxy(pageIndex, pageInfo, this); + this.pageCache[pageIndex] = page; + return page; + }.bind(this)); this.pagePromises[pageIndex] = promise; - this.messageHandler.send('GetPageRequest', { pageIndex: pageIndex }); return promise; }, getPageIndex: function WorkerTransport_getPageIndexByRef(ref) { - var promise = new PDFJS.LegacyPromise(); - this.messageHandler.send('GetPageIndex', { ref: ref }, - function (pageIndex) { - promise.resolve(pageIndex); - } - ); - return promise; + return this.messageHandler.sendWithPromise('GetPageIndex', { ref: ref }); }, getAnnotations: function WorkerTransport_getAnnotations(pageIndex) { - this.messageHandler.send('GetAnnotationsRequest', + return this.messageHandler.sendWithPromise('GetAnnotations', { pageIndex: pageIndex }); }, getDestinations: function WorkerTransport_getDestinations() { - var promise = new PDFJS.LegacyPromise(); - this.messageHandler.send('GetDestinations', null, - function transportDestinations(destinations) { - promise.resolve(destinations); - } - ); - return promise; + return this.messageHandler.sendWithPromise('GetDestinations', null); + }, + + getAttachments: function WorkerTransport_getAttachments() { + return this.messageHandler.sendWithPromise('GetAttachments', null); + }, + + getJavaScript: function WorkerTransport_getJavaScript() { + return this.messageHandler.sendWithPromise('GetJavaScript', null); + }, + + getOutline: function WorkerTransport_getOutline() { + return this.messageHandler.sendWithPromise('GetOutline', null); + }, + + getMetadata: function WorkerTransport_getMetadata() { + return this.messageHandler.sendWithPromise('GetMetadata', null). + then(function transportMetadata(results) { + return { + info: results[0], + metadata: (results[1] ? new PDFJS.Metadata(results[1]) : null) + }; + }); }, startCleanup: function WorkerTransport_startCleanup() { - this.messageHandler.send('Cleanup', null, - function endCleanup() { - for (var i = 0, ii = this.pageCache.length; i < ii; i++) { - var page = this.pageCache[i]; - if (page) { - page.destroy(); - } + this.messageHandler.sendWithPromise('Cleanup', null). + then(function endCleanup() { + for (var i = 0, ii = this.pageCache.length; i < ii; i++) { + var page = this.pageCache[i]; + if (page) { + page.destroy(); } - this.commonObjs.clear(); - FontLoader.clear(); - }.bind(this) - ); + } + this.commonObjs.clear(); + FontLoader.clear(); + }.bind(this)); } }; return WorkerTransport; @@ -4823,7 +3412,7 @@ var PDFObjects = (function PDFObjectsClosure() { } var obj = { - promise: new LegacyPromise(), + capability: createPromiseCapability(), data: null, resolved: false }; @@ -4845,7 +3434,7 @@ var PDFObjects = (function PDFObjectsClosure() { // If there is a callback, then the get can be async and the object is // not required to be resolved right now if (callback) { - this.ensureObj(objId).promise.then(callback); + this.ensureObj(objId).capability.promise.then(callback); return null; } @@ -4870,7 +3459,7 @@ var PDFObjects = (function PDFObjectsClosure() { obj.resolved = true; obj.data = data; - obj.promise.resolve(data); + obj.capability.resolve(data); }, isResolved: function PDFObjects_isResolved(objId) { @@ -4917,7 +3506,7 @@ var RenderTask = (function RenderTaskClosure() { * Promise for rendering task completion. * @type {Promise} */ - this.promise = new PDFJS.LegacyPromise(); + this.promise = this.internalRenderTask.capability.promise; } RenderTask.prototype = /** @lends RenderTask.prototype */ { @@ -4928,7 +3517,18 @@ var RenderTask = (function RenderTaskClosure() { */ cancel: function RenderTask_cancel() { this.internalRenderTask.cancel(); - this.promise.reject(new Error('Rendering is cancelled')); + }, + + /** + * Registers callback to indicate the rendering task completion. + * + * @param {function} onFulfilled The callback for the rendering completion. + * @param {function} onRejected The callback for the rendering failure. + * @return {Promise} A promise that is resolved after the onFulfilled or + * onRejected callback. + */ + then: function RenderTask_then(onFulfilled, onRejected) { + return this.promise.then(onFulfilled, onRejected); } }; @@ -4954,6 +3554,11 @@ var InternalRenderTask = (function InternalRenderTaskClosure() { this.graphicsReadyCallback = null; this.graphicsReady = false; this.cancelled = false; + this.capability = createPromiseCapability(); + // caching this-bound methods + this._continueBound = this._continue.bind(this); + this._scheduleNextBound = this._scheduleNext.bind(this); + this._nextBound = this._next.bind(this); } InternalRenderTask.prototype = { @@ -4973,8 +3578,7 @@ var InternalRenderTask = (function InternalRenderTaskClosure() { var params = this.params; this.gfx = new CanvasGraphics(params.canvasContext, this.commonObjs, - this.objs, params.textLayer, - params.imageLayer); + this.objs, params.imageLayer); this.gfx.beginDrawing(params.viewport, transparency); this.operatorListIdx = 0; @@ -4993,7 +3597,7 @@ var InternalRenderTask = (function InternalRenderTaskClosure() { operatorListChanged: function InternalRenderTask_operatorListChanged() { if (!this.graphicsReady) { if (!this.graphicsReadyCallback) { - this.graphicsReadyCallback = this._continue.bind(this); + this.graphicsReadyCallback = this._continueBound; } return; } @@ -5014,19 +3618,23 @@ var InternalRenderTask = (function InternalRenderTaskClosure() { return; } if (this.params.continueCallback) { - this.params.continueCallback(this._next.bind(this)); + this.params.continueCallback(this._scheduleNextBound); } else { - this._next(); + this._scheduleNext(); } }, + _scheduleNext: function InternalRenderTask__scheduleNext() { + window.requestAnimationFrame(this._nextBound); + }, + _next: function InternalRenderTask__next() { if (this.cancelled) { return; } this.operatorListIdx = this.gfx.executeOperatorList(this.operatorList, this.operatorListIdx, - this._continue.bind(this), + this._continueBound, this.stepper); if (this.operatorListIdx === this.operatorList.argsArray.length) { this.running = false; @@ -5129,6 +3737,7 @@ var Metadata = PDFJS.Metadata = (function MetadataClosure() { // Minimal font size that would be used during canvas fillText operations. var MIN_FONT_SIZE = 16; +var MAX_GROUP_SIZE = 4096; var COMPILE_TYPE3_GLYPHS = true; @@ -5453,6 +4062,7 @@ var CanvasExtraState = (function CanvasExtraStateClosure() { this.fontSize = 0; this.fontSizeScale = 1; this.textMatrix = IDENTITY_MATRIX; + this.textMatrixScale = 1; this.fontMatrix = FONT_IDENTITY_MATRIX; this.leading = 0; // Current point (in user coordinates) @@ -5467,13 +4077,6 @@ var CanvasExtraState = (function CanvasExtraStateClosure() { this.textHScale = 1; this.textRenderingMode = TextRenderingMode.FILL; this.textRise = 0; - // Color spaces - this.fillColorSpace = ColorSpace.singletons.gray; - this.fillColorSpaceObj = null; - this.strokeColorSpace = ColorSpace.singletons.gray; - this.strokeColorSpaceObj = null; - this.fillColorObj = null; - this.strokeColorObj = null; // Default fore and background colors this.fillColor = '#000000'; this.strokeColor = '#000000'; @@ -5503,7 +4106,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { // before it stops and shedules a continue of execution. var EXECUTION_TIME = 15; - function CanvasGraphics(canvasCtx, commonObjs, objs, textLayer, imageLayer) { + function CanvasGraphics(canvasCtx, commonObjs, objs, imageLayer) { this.ctx = canvasCtx; this.current = new CanvasExtraState(); this.stateStack = []; @@ -5513,7 +4116,6 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { this.xobjs = null; this.commonObjs = commonObjs; this.objs = objs; - this.textLayer = textLayer; this.imageLayer = imageLayer; this.groupStack = []; this.processingType3 = null; @@ -5555,83 +4157,92 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { var partialChunkHeight = height - fullChunks * fullChunkHeight; var chunkImgData = ctx.createImageData(width, fullChunkHeight); - var srcPos = 0; + var srcPos = 0, destPos; var src = imgData.data; var dest = chunkImgData.data; + var i, j, thisChunkHeight, elemsInThisChunk; // There are multiple forms in which the pixel data can be passed, and // imgData.kind tells us which one this is. - if (imgData.kind === ImageKind.GRAYSCALE_1BPP) { // Grayscale, 1 bit per pixel (i.e. black-and-white). - var destDataLength = dest.length; var srcLength = src.byteLength; - for (var i = 3; i < destDataLength; i += 4) { - dest[i] = 255; - } - for (var i = 0; i < totalChunks; i++) { - var thisChunkHeight = + var dest32 = PDFJS.hasCanvasTypedArrays ? new Uint32Array(dest.buffer) : + new Uint32ArrayView(dest); + var dest32DataLength = dest32.length; + var fullSrcDiff = (width + 7) >> 3; + var white = 0xFFFFFFFF; + var black = (PDFJS.isLittleEndian || !PDFJS.hasCanvasTypedArrays) ? + 0xFF000000 : 0x000000FF; + for (i = 0; i < totalChunks; i++) { + thisChunkHeight = (i < fullChunks) ? fullChunkHeight : partialChunkHeight; - var destPos = 0; - for (var j = 0; j < thisChunkHeight; j++) { + destPos = 0; + for (j = 0; j < thisChunkHeight; j++) { + var srcDiff = srcLength - srcPos; + var k = 0; + var kEnd = (srcDiff > fullSrcDiff) ? width : srcDiff * 8 - 7; + var kEndUnrolled = kEnd & ~7; var mask = 0; var srcByte = 0; - for (var k = 0; k < width; k++, destPos += 4) { - if (mask === 0) { - if (srcPos >= srcLength) { - break; - } - srcByte = src[srcPos++]; - mask = 128; - } - - if ((srcByte & mask)) { - dest[destPos] = 255; - dest[destPos + 1] = 255; - dest[destPos + 2] = 255; - } else { - dest[destPos] = 0; - dest[destPos + 1] = 0; - dest[destPos + 2] = 0; - } + for (; k < kEndUnrolled; k += 8) { + srcByte = src[srcPos++]; + dest32[destPos++] = (srcByte & 128) ? white : black; + dest32[destPos++] = (srcByte & 64) ? white : black; + dest32[destPos++] = (srcByte & 32) ? white : black; + dest32[destPos++] = (srcByte & 16) ? white : black; + dest32[destPos++] = (srcByte & 8) ? white : black; + dest32[destPos++] = (srcByte & 4) ? white : black; + dest32[destPos++] = (srcByte & 2) ? white : black; + dest32[destPos++] = (srcByte & 1) ? white : black; + } + for (; k < kEnd; k++) { + if (mask === 0) { + srcByte = src[srcPos++]; + mask = 128; + } + dest32[destPos++] = (srcByte & mask) ? white : black; mask >>= 1; } } - if (destPos < destDataLength) { - // We ran out of input. Make all remaining pixels transparent. - destPos += 3; - do { - dest[destPos] = 0; - destPos += 4; - } while (destPos < destDataLength); + // We ran out of input. Make all remaining pixels transparent. + while (destPos < dest32DataLength) { + dest32[destPos++] = 0; } ctx.putImageData(chunkImgData, 0, i * fullChunkHeight); } - } else if (imgData.kind === ImageKind.RGBA_32BPP) { // RGBA, 32-bits per pixel. - for (var i = 0; i < totalChunks; i++) { - var thisChunkHeight = - (i < fullChunks) ? fullChunkHeight : partialChunkHeight; - var elemsInThisChunk = imgData.width * thisChunkHeight * 4; - + j = 0; + elemsInThisChunk = width * fullChunkHeight * 4; + for (i = 0; i < fullChunks; i++) { dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk)); srcPos += elemsInThisChunk; - ctx.putImageData(chunkImgData, 0, i * fullChunkHeight); + ctx.putImageData(chunkImgData, 0, j); + j += fullChunkHeight; + } + if (i < totalChunks) { + elemsInThisChunk = width * partialChunkHeight * 4; + dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk)); + ctx.putImageData(chunkImgData, 0, j); } } else if (imgData.kind === ImageKind.RGB_24BPP) { // RGB, 24-bits per pixel. - for (var i = 0; i < totalChunks; i++) { - var thisChunkHeight = - (i < fullChunks) ? fullChunkHeight : partialChunkHeight; - var elemsInThisChunk = imgData.width * thisChunkHeight * 3; - var destPos = 0; - for (var j = 0; j < elemsInThisChunk; j += 3) { + thisChunkHeight = fullChunkHeight; + elemsInThisChunk = width * thisChunkHeight; + for (i = 0; i < totalChunks; i++) { + if (i >= fullChunks) { + thisChunkHeight =partialChunkHeight; + elemsInThisChunk = width * thisChunkHeight; + } + + destPos = 0; + for (j = elemsInThisChunk; j--;) { dest[destPos++] = src[srcPos++]; dest[destPos++] = src[srcPos++]; dest[destPos++] = src[srcPos++]; @@ -5639,9 +4250,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { } ctx.putImageData(chunkImgData, 0, i * fullChunkHeight); } - } else { - error('bad image kind: ' + imgData.kind); + error('bad image kind: ' + imgData.kind); } } @@ -5700,15 +4310,10 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { } } - function composeSMask(ctx, smask, layerCtx) { - var mask = smask.canvas; - var maskCtx = smask.context; - var width = mask.width, height = mask.height; - + function genericComposeSMask(maskCtx, layerCtx, width, height, + subtype, backdrop) { var addBackdropFn; - if (smask.backdrop) { - var cs = smask.colorSpace || ColorSpace.singletons.rgb; - var backdrop = cs.getRgb(smask.backdrop, 0); + if (backdrop) { addBackdropFn = function (r0, g0, b0, bytes) { var length = bytes.length; for (var i = 3; i < length; i += 4) { @@ -5730,7 +4335,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { } var composeFn; - if (smask.subtype === 'Luminosity') { + if (subtype === 'Luminosity') { composeFn = function (maskDataBytes, layerDataBytes) { var length = maskDataBytes.length; for (var i = 3; i < length; i += 4) { @@ -5751,7 +4356,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { } // processing image in chunks to save memory - var chunkSize = 16; + var PIXELS_TO_PROCESS = 65536; + var chunkSize = Math.min(height, Math.ceil(PIXELS_TO_PROCESS / width)); for (var row = 0; row < height; row += chunkSize) { var chunkHeight = Math.min(chunkSize, height - row); var maskData = maskCtx.getImageData(0, row, width, chunkHeight); @@ -5762,9 +4368,26 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { maskCtx.putImageData(layerData, 0, row); } + } - ctx.setTransform(1, 0, 0, 1, 0, 0); - ctx.drawImage(mask, smask.offsetX, smask.offsetY); + function composeSMask(ctx, smask, layerCtx) { + var mask = smask.canvas; + var maskCtx = smask.context; + + ctx.setTransform(smask.scaleX, 0, 0, smask.scaleY, + smask.offsetX, smask.offsetY); + + var backdrop = smask.backdrop || null; + if (WebGLUtils.isEnabled) { + var composed = WebGLUtils.composeSMask(layerCtx.canvas, mask, + {subtype: smask.subtype, backdrop: backdrop}); + ctx.setTransform(1, 0, 0, 1, 0, 0); + ctx.drawImage(composed, smask.offsetX, smask.offsetY); + return; + } + genericComposeSMask(maskCtx, layerCtx, mask.width, mask.height, + smask.subtype, backdrop); + ctx.drawImage(mask, 0, 0); } var LINE_CAP_STYLES = ['butt', 'round', 'square']; @@ -5799,9 +4422,6 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { this.baseTransform = this.ctx.mozCurrentTransform.slice(); - if (this.textLayer) { - this.textLayer.beginLayout(); - } if (this.imageLayer) { this.imageLayer.beginLayout(); } @@ -5821,13 +4441,11 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { return i; } - var executionEndIdx; var endTime = Date.now() + EXECUTION_TIME; var commonObjs = this.commonObjs; var objs = this.objs; var fnId; - var deferred = Promise.resolve(); while (true) { if (stepper && i === stepper.nextBreakPoint) { @@ -5865,11 +4483,10 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { return i; } - // If the execution took longer then a certain amount of time, schedule - // to continue exeution after a short delay. - // However, this is only possible if a 'continueCallback' is passed in. + // If the execution took longer then a certain amount of time and + // `continueCallback` is specified, interrupt the execution. if (continueCallback && Date.now() > endTime) { - deferred.then(continueCallback); + continueCallback(); return i; } @@ -5881,10 +4498,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { endDrawing: function CanvasGraphics_endDrawing() { this.ctx.restore(); CachedCanvases.clear(); + WebGLUtils.clear(); - if (this.textLayer) { - this.textLayer.endLayout(); - } if (this.imageLayer) { this.imageLayer.endLayout(); } @@ -6004,6 +4619,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { this.ctx.save(); var groupCtx = scratchCanvas.context; + groupCtx.scale(1 / activeSMask.scaleX, 1 / activeSMask.scaleY); groupCtx.translate(-activeSMask.offsetX, -activeSMask.offsetY); groupCtx.transform.apply(groupCtx, currentTransform); @@ -6050,31 +4666,60 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { }, // Path - moveTo: function CanvasGraphics_moveTo(x, y) { - this.ctx.moveTo(x, y); - this.current.setCurrentPoint(x, y); - }, - lineTo: function CanvasGraphics_lineTo(x, y) { - this.ctx.lineTo(x, y); - this.current.setCurrentPoint(x, y); - }, - curveTo: function CanvasGraphics_curveTo(x1, y1, x2, y2, x3, y3) { - this.ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3); - this.current.setCurrentPoint(x3, y3); - }, - curveTo2: function CanvasGraphics_curveTo2(x2, y2, x3, y3) { + constructPath: function CanvasGraphics_constructPath(ops, args) { + var ctx = this.ctx; var current = this.current; - this.ctx.bezierCurveTo(current.x, current.y, x2, y2, x3, y3); - current.setCurrentPoint(x3, y3); - }, - curveTo3: function CanvasGraphics_curveTo3(x1, y1, x3, y3) { - this.curveTo(x1, y1, x3, y3, x3, y3); - this.current.setCurrentPoint(x3, y3); + var x = current.x, y = current.y; + for (var i = 0, j = 0, ii = ops.length; i < ii; i++) { + switch (ops[i] | 0) { + case OPS.moveTo: + x = args[j++]; + y = args[j++]; + ctx.moveTo(x, y); + break; + case OPS.lineTo: + x = args[j++]; + y = args[j++]; + ctx.lineTo(x, y); + break; + case OPS.curveTo: + x = args[j + 4]; + y = args[j + 5]; + ctx.bezierCurveTo(args[j], args[j + 1], args[j + 2], args[j + 3], + x, y); + j += 6; + break; + case OPS.curveTo2: + ctx.bezierCurveTo(x, y, args[j], args[j + 1], + args[j + 2], args[j + 3]); + x = args[j + 2]; + y = args[j + 3]; + j += 4; + break; + case OPS.curveTo3: + x = args[j + 2]; + y = args[j + 3]; + ctx.bezierCurveTo(args[j], args[j + 1], x, y, x, y); + j += 4; + break; + case OPS.closePath: + ctx.closePath(); + break; + } + } + current.setCurrentPoint(x, y); }, closePath: function CanvasGraphics_closePath() { this.ctx.closePath(); }, rectangle: function CanvasGraphics_rectangle(x, y, width, height) { + if (width === 0) { + width = this.getSinglePixelWidth(); + } + if (height === 0) { + height = this.getSinglePixelWidth(); + } + this.ctx.rect(x, y, width, height); }, stroke: function CanvasGraphics_stroke(consumePath) { @@ -6122,21 +4767,21 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { } if (this.pendingEOFill) { - if ('mozFillRule' in this.ctx) { - this.ctx.mozFillRule = 'evenodd'; - this.ctx.fill(); - this.ctx.mozFillRule = 'nonzero'; + if (ctx.mozFillRule !== undefined) { + ctx.mozFillRule = 'evenodd'; + ctx.fill(); + ctx.mozFillRule = 'nonzero'; } else { try { - this.ctx.fill('evenodd'); + ctx.fill('evenodd'); } catch (ex) { // shouldn't really happen, but browsers might think differently - this.ctx.fill(); + ctx.fill(); } } this.pendingEOFill = false; } else { - this.ctx.fill(); + ctx.fill(); } if (needRestore) { @@ -6184,16 +4829,17 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { // Text beginText: function CanvasGraphics_beginText() { this.current.textMatrix = IDENTITY_MATRIX; + this.current.textMatrixScale = 1; this.current.x = this.current.lineX = 0; this.current.y = this.current.lineY = 0; }, endText: function CanvasGraphics_endText() { - if (!('pendingTextPaths' in this)) { - this.ctx.beginPath(); - return; - } var paths = this.pendingTextPaths; var ctx = this.ctx; + if (paths === undefined) { + ctx.beginPath(); + return; + } ctx.save(); ctx.beginPath(); @@ -6250,7 +4896,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { this.current.font = fontObj; this.current.fontSize = size; - if (fontObj.coded) { + if (fontObj.isType3Font) { return; // we don't need ctx.font for Type3 fonts } @@ -6288,6 +4934,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { }, setTextMatrix: function CanvasGraphics_setTextMatrix(a, b, c, d, e, f) { this.current.textMatrix = [a, b, c, d, e, f]; + this.current.textMatrixScale = Math.sqrt(a * a + b * b); this.current.x = this.current.lineX = 0; this.current.y = this.current.lineY = 0; @@ -6295,51 +4942,13 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { nextLine: function CanvasGraphics_nextLine() { this.moveText(0, this.current.leading); }, - applyTextTransforms: function CanvasGraphics_applyTextTransforms() { - var ctx = this.ctx; - var current = this.current; - ctx.transform.apply(ctx, current.textMatrix); - ctx.translate(current.x, current.y + current.textRise); - if (current.fontDirection > 0) { - ctx.scale(current.textHScale, -1); - } else { - ctx.scale(-current.textHScale, 1); - } - }, - createTextGeometry: function CanvasGraphics_createTextGeometry() { - var geometry = {}; - var ctx = this.ctx; - var font = this.current.font; - var ctxMatrix = ctx.mozCurrentTransform; - var a = ctxMatrix[0], b = ctxMatrix[1], c = ctxMatrix[2]; - var d = ctxMatrix[3], e = ctxMatrix[4], f = ctxMatrix[5]; - var sx = (a >= 0) ? - Math.sqrt((a * a) + (b * b)) : -Math.sqrt((a * a) + (b * b)); - var sy = (d >= 0) ? - Math.sqrt((c * c) + (d * d)) : -Math.sqrt((c * c) + (d * d)); - var angle = Math.atan2(b, a); - var x = e; - var y = f; - geometry.x = x; - geometry.y = y; - geometry.hScale = sx; - geometry.vScale = sy; - geometry.angle = angle; - geometry.spaceWidth = font.spaceWidth; - geometry.fontName = font.loadedName; - geometry.fontFamily = font.fallbackName; - geometry.fontSize = this.current.fontSize; - geometry.ascent = font.ascent; - geometry.descent = font.descent; - return geometry; - }, - paintChar: function (character, x, y) { + paintChar: function CanvasGraphics_paintChar(character, x, y) { var ctx = this.ctx; var current = this.current; var font = current.font; - var fontSize = current.fontSize / current.fontSizeScale; var textRenderingMode = current.textRenderingMode; + var fontSize = current.fontSize / current.fontSizeScale; var fillStrokeMode = textRenderingMode & TextRenderingMode.FILL_STROKE_MASK; var isAddToPathSet = !!(textRenderingMode & @@ -6404,236 +5013,183 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { return shadow(this, 'isFontSubpixelAAEnabled', enabled); }, - showText: function CanvasGraphics_showText(glyphs, skipTextSelection) { - var ctx = this.ctx; + showText: function CanvasGraphics_showText(glyphs) { var current = this.current; var font = current.font; + if (font.isType3Font) { + return this.showType3Text(glyphs); + } + var fontSize = current.fontSize; + if (fontSize === 0) { + return; + } + + var ctx = this.ctx; var fontSizeScale = current.fontSizeScale; var charSpacing = current.charSpacing; var wordSpacing = current.wordSpacing; - var textHScale = current.textHScale * current.fontDirection; - var fontMatrix = current.fontMatrix || FONT_IDENTITY_MATRIX; + var fontDirection = current.fontDirection; + var textHScale = current.textHScale * fontDirection; var glyphsLength = glyphs.length; - var textLayer = this.textLayer; - var geom; - var textSelection = textLayer && !skipTextSelection ? true : false; - var canvasWidth = 0.0; var vertical = font.vertical; var defaultVMetrics = font.defaultVMetrics; + var widthAdvanceScale = fontSize * current.fontMatrix[0]; - // Type3 fonts - each glyph is a "mini-PDF" - if (font.coded) { - ctx.save(); - ctx.transform.apply(ctx, current.textMatrix); - ctx.translate(current.x, current.y); - - ctx.scale(textHScale, 1); + var simpleFillText = + current.textRenderingMode === TextRenderingMode.FILL && + !font.disableFontFace; - if (textSelection) { - this.save(); - ctx.scale(1, -1); - geom = this.createTextGeometry(); - this.restore(); - } - for (var i = 0; i < glyphsLength; ++i) { + ctx.save(); + ctx.transform.apply(ctx, current.textMatrix); + ctx.translate(current.x, current.y + current.textRise); - var glyph = glyphs[i]; - if (glyph === null) { - // word break - this.ctx.translate(wordSpacing, 0); - current.x += wordSpacing * textHScale; - continue; - } + if (fontDirection > 0) { + ctx.scale(textHScale, -1); + } else { + ctx.scale(textHScale, 1); + } - this.processingType3 = glyph; - this.save(); - ctx.scale(fontSize, fontSize); - ctx.transform.apply(ctx, fontMatrix); - this.executeOperatorList(glyph.operatorList); - this.restore(); + var lineWidth = current.lineWidth; + var scale = current.textMatrixScale; + if (scale === 0 || lineWidth === 0) { + lineWidth = this.getSinglePixelWidth(); + } else { + lineWidth /= scale; + } - var transformed = Util.applyTransform([glyph.width, 0], fontMatrix); - var width = (transformed[0] * fontSize + charSpacing) * - current.fontDirection; + if (fontSizeScale != 1.0) { + ctx.scale(fontSizeScale, fontSizeScale); + lineWidth /= fontSizeScale; + } - ctx.translate(width, 0); - current.x += width * textHScale; + ctx.lineWidth = lineWidth; - canvasWidth += width; + var x = 0, i; + for (i = 0; i < glyphsLength; ++i) { + var glyph = glyphs[i]; + if (glyph === null) { + // word break + x += fontDirection * wordSpacing; + continue; + } else if (isNum(glyph)) { + x += -glyph * fontSize * 0.001; + continue; } - ctx.restore(); - this.processingType3 = null; - } else { - ctx.save(); - this.applyTextTransforms(); - var lineWidth = current.lineWidth; - var a1 = current.textMatrix[0], b1 = current.textMatrix[1]; - var scale = Math.sqrt(a1 * a1 + b1 * b1); - if (scale === 0 || lineWidth === 0) { - lineWidth = this.getSinglePixelWidth(); - } else { - lineWidth /= scale; - } + var restoreNeeded = false; + var character = glyph.fontChar; + var accent = glyph.accent; + var scaledX, scaledY, scaledAccentX, scaledAccentY; + var width = glyph.width; + if (vertical) { + var vmetric, vx, vy; + vmetric = glyph.vmetric || defaultVMetrics; + vx = glyph.vmetric ? vmetric[1] : width * 0.5; + vx = -vx * widthAdvanceScale; + vy = vmetric[2] * widthAdvanceScale; - if (textSelection) { - geom = this.createTextGeometry(); + width = vmetric ? -vmetric[0] : width; + scaledX = vx / fontSizeScale; + scaledY = (x + vy) / fontSizeScale; + } else { + scaledX = x / fontSizeScale; + scaledY = 0; } - if (fontSizeScale != 1.0) { - ctx.scale(fontSizeScale, fontSizeScale); - lineWidth /= fontSizeScale; + if (font.remeasure && width > 0 && this.isFontSubpixelAAEnabled) { + // some standard fonts may not have the exact width, trying to + // rescale per character + var measuredWidth = ctx.measureText(character).width * 1000 / + fontSize * fontSizeScale; + var characterScaleX = width / measuredWidth; + restoreNeeded = true; + ctx.save(); + ctx.scale(characterScaleX, 1); + scaledX /= characterScaleX; } - ctx.lineWidth = lineWidth; - - var x = 0; - for (var i = 0; i < glyphsLength; ++i) { - var glyph = glyphs[i]; - if (glyph === null) { - // word break - x += current.fontDirection * wordSpacing; - continue; - } - - var restoreNeeded = false; - var character = glyph.fontChar; - var vmetric = glyph.vmetric || defaultVMetrics; - if (vertical) { - var vx = glyph.vmetric ? vmetric[1] : glyph.width * 0.5; - vx = -vx * fontSize * current.fontMatrix[0]; - var vy = vmetric[2] * fontSize * current.fontMatrix[0]; - } - var width = vmetric ? -vmetric[0] : glyph.width; - var charWidth = width * fontSize * current.fontMatrix[0] + - charSpacing * current.fontDirection; - var accent = glyph.accent; - - var scaledX, scaledY, scaledAccentX, scaledAccentY; - - if (vertical) { - scaledX = vx / fontSizeScale; - scaledY = (x + vy) / fontSizeScale; - } else { - scaledX = x / fontSizeScale; - scaledY = 0; - } - - if (font.remeasure && width > 0 && this.isFontSubpixelAAEnabled) { - // some standard fonts may not have the exact width, trying to - // rescale per character - var measuredWidth = ctx.measureText(character).width * 1000 / - current.fontSize * current.fontSizeScale; - var characterScaleX = width / measuredWidth; - restoreNeeded = true; - ctx.save(); - ctx.scale(characterScaleX, 1); - scaledX /= characterScaleX; - if (accent) { - scaledAccentX /= characterScaleX; - } - } - + if (simpleFillText && !accent) { + // common case + ctx.fillText(character, scaledX, scaledY); + } else { this.paintChar(character, scaledX, scaledY); if (accent) { scaledAccentX = scaledX + accent.offset.x / fontSizeScale; scaledAccentY = scaledY - accent.offset.y / fontSizeScale; this.paintChar(accent.fontChar, scaledAccentX, scaledAccentY); } + } - x += charWidth; - - canvasWidth += charWidth; + var charWidth = width * widthAdvanceScale + charSpacing * fontDirection; + x += charWidth; - if (restoreNeeded) { - ctx.restore(); - } + if (restoreNeeded) { + ctx.restore(); } - if (vertical) { - current.y -= x * textHScale; - } else { - current.x += x * textHScale; - } - ctx.restore(); } - - if (textSelection) { - geom.canvasWidth = canvasWidth; - if (vertical) { - var VERTICAL_TEXT_ROTATION = Math.PI / 2; - geom.angle += VERTICAL_TEXT_ROTATION; - } - this.textLayer.appendText(geom); + if (vertical) { + current.y -= x * textHScale; + } else { + current.x += x * textHScale; } - - return canvasWidth; + ctx.restore(); }, - showSpacedText: function CanvasGraphics_showSpacedText(arr) { + + showType3Text: function CanvasGraphics_showType3Text(glyphs) { + // Type3 fonts - each glyph is a "mini-PDF" var ctx = this.ctx; var current = this.current; var font = current.font; var fontSize = current.fontSize; - // TJ array's number is independent from fontMatrix - var textHScale = current.textHScale * 0.001 * current.fontDirection; - var arrLength = arr.length; - var textLayer = this.textLayer; - var geom; - var canvasWidth = 0.0; - var textSelection = textLayer ? true : false; - var vertical = font.vertical; - var spacingAccumulator = 0; + var fontDirection = current.fontDirection; + var charSpacing = current.charSpacing; + var wordSpacing = current.wordSpacing; + var textHScale = current.textHScale * fontDirection; + var fontMatrix = current.fontMatrix || FONT_IDENTITY_MATRIX; + var glyphsLength = glyphs.length; + var i, glyph, width; - if (textSelection) { - ctx.save(); - this.applyTextTransforms(); - geom = this.createTextGeometry(); - ctx.restore(); + if (fontSize === 0) { + return; } - for (var i = 0; i < arrLength; ++i) { - var e = arr[i]; - if (isNum(e)) { - var spacingLength = -e * fontSize * textHScale; - if (vertical) { - current.y += spacingLength; - } else { - current.x += spacingLength; - } + ctx.save(); + ctx.transform.apply(ctx, current.textMatrix); + ctx.translate(current.x, current.y); - if (textSelection) { - spacingAccumulator += spacingLength; - } - } else { - var shownCanvasWidth = this.showText(e, true); + ctx.scale(textHScale, 1); - if (textSelection) { - canvasWidth += spacingAccumulator + shownCanvasWidth; - spacingAccumulator = 0; - } + for (i = 0; i < glyphsLength; ++i) { + glyph = glyphs[i]; + if (glyph === null) { + // word break + this.ctx.translate(wordSpacing, 0); + current.x += wordSpacing * textHScale; + continue; + } else if (isNum(glyph)) { + var spacingLength = -glyph * 0.001 * fontSize; + this.ctx.translate(spacingLength, 0); + current.x += spacingLength * textHScale; + continue; } - } - if (textSelection) { - geom.canvasWidth = canvasWidth; - if (vertical) { - var VERTICAL_TEXT_ROTATION = Math.PI / 2; - geom.angle += VERTICAL_TEXT_ROTATION; - } - this.textLayer.appendText(geom); + this.processingType3 = glyph; + this.save(); + ctx.scale(fontSize, fontSize); + ctx.transform.apply(ctx, fontMatrix); + var operatorList = font.charProcOperatorList[glyph.operatorListId]; + this.executeOperatorList(operatorList); + this.restore(); + + var transformed = Util.applyTransform([glyph.width, 0], fontMatrix); + width = ((transformed[0] * fontSize + charSpacing) * fontDirection); + + ctx.translate(width, 0); + current.x += width * textHScale; } - }, - nextLineShowText: function CanvasGraphics_nextLineShowText(text) { - this.nextLine(); - this.showText(text); - }, - nextLineSetSpacingShowText: - function CanvasGraphics_nextLineSetSpacingShowText(wordSpacing, - charSpacing, - text) { - this.setWordSpacing(wordSpacing); - this.setCharSpacing(charSpacing); - this.nextLineShowText(text); + ctx.restore(); + this.processingType3 = null; }, // Type3 fonts @@ -6655,104 +5211,30 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { }, // Color - setStrokeColorSpace: function CanvasGraphics_setStrokeColorSpace(raw) { - this.current.strokeColorSpace = ColorSpace.fromIR(raw); - }, - setFillColorSpace: function CanvasGraphics_setFillColorSpace(raw) { - this.current.fillColorSpace = ColorSpace.fromIR(raw); - }, - setStrokeColor: function CanvasGraphics_setStrokeColor(/*...*/) { - var cs = this.current.strokeColorSpace; - var rgbColor = cs.getRgb(arguments, 0); - var color = Util.makeCssRgb(rgbColor); - this.ctx.strokeStyle = color; - this.current.strokeColor = color; - }, - getColorN_Pattern: function CanvasGraphics_getColorN_Pattern(IR, cs) { + getColorN_Pattern: function CanvasGraphics_getColorN_Pattern(IR) { + var pattern; if (IR[0] == 'TilingPattern') { - var args = IR[1]; - var base = cs.base; - var color; - if (base) { - var baseComps = base.numComps; - - color = base.getRgb(args, 0); - } - var pattern = new TilingPattern(IR, color, this.ctx, this.objs, - this.commonObjs, this.baseTransform); + var color = IR[1]; + pattern = new TilingPattern(IR, color, this.ctx, this.objs, + this.commonObjs, this.baseTransform); } else { - var pattern = getShadingPatternFromIR(IR); + pattern = getShadingPatternFromIR(IR); } return pattern; }, setStrokeColorN: function CanvasGraphics_setStrokeColorN(/*...*/) { - var cs = this.current.strokeColorSpace; - - if (cs.name == 'Pattern') { - this.current.strokeColor = this.getColorN_Pattern(arguments, cs); - } else { - this.setStrokeColor.apply(this, arguments); - } - }, - setFillColor: function CanvasGraphics_setFillColor(/*...*/) { - var cs = this.current.fillColorSpace; - var rgbColor = cs.getRgb(arguments, 0); - var color = Util.makeCssRgb(rgbColor); - this.ctx.fillStyle = color; - this.current.fillColor = color; + this.current.strokeColor = this.getColorN_Pattern(arguments); }, setFillColorN: function CanvasGraphics_setFillColorN(/*...*/) { - var cs = this.current.fillColorSpace; - - if (cs.name == 'Pattern') { - this.current.fillColor = this.getColorN_Pattern(arguments, cs); - } else { - this.setFillColor.apply(this, arguments); - } - }, - setStrokeGray: function CanvasGraphics_setStrokeGray(gray) { - this.current.strokeColorSpace = ColorSpace.singletons.gray; - - var rgbColor = this.current.strokeColorSpace.getRgb(arguments, 0); - var color = Util.makeCssRgb(rgbColor); - this.ctx.strokeStyle = color; - this.current.strokeColor = color; - }, - setFillGray: function CanvasGraphics_setFillGray(gray) { - this.current.fillColorSpace = ColorSpace.singletons.gray; - - var rgbColor = this.current.fillColorSpace.getRgb(arguments, 0); - var color = Util.makeCssRgb(rgbColor); - this.ctx.fillStyle = color; - this.current.fillColor = color; + this.current.fillColor = this.getColorN_Pattern(arguments); }, setStrokeRGBColor: function CanvasGraphics_setStrokeRGBColor(r, g, b) { - this.current.strokeColorSpace = ColorSpace.singletons.rgb; - - var rgbColor = this.current.strokeColorSpace.getRgb(arguments, 0); - var color = Util.makeCssRgb(rgbColor); + var color = Util.makeCssRgb(arguments); this.ctx.strokeStyle = color; this.current.strokeColor = color; }, setFillRGBColor: function CanvasGraphics_setFillRGBColor(r, g, b) { - this.current.fillColorSpace = ColorSpace.singletons.rgb; - - var rgbColor = this.current.fillColorSpace.getRgb(arguments, 0); - var color = Util.makeCssRgb(rgbColor); - this.ctx.fillStyle = color; - this.current.fillColor = color; - }, - setStrokeCMYKColor: function CanvasGraphics_setStrokeCMYKColor(c, m, y, k) { - this.current.strokeColorSpace = ColorSpace.singletons.cmyk; - - var color = Util.makeCssCmyk(arguments); - this.ctx.strokeStyle = color; - this.current.strokeColor = color; - }, - setFillCMYKColor: function CanvasGraphics_setFillCMYKColor(c, m, y, k) { - this.current.fillColorSpace = ColorSpace.singletons.cmyk; - - var color = Util.makeCssCmyk(arguments); + var color = Util.makeCssRgb(arguments); this.ctx.fillStyle = color; this.current.fillColor = color; }, @@ -6872,8 +5354,19 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { bounds = Util.intersect(bounds, canvasBounds) || [0, 0, 0, 0]; // Use ceil in case we're between sizes so we don't create canvas that is // too small and make the canvas at least 1x1 pixels. - var drawnWidth = Math.max(Math.ceil(bounds[2] - bounds[0]), 1); - var drawnHeight = Math.max(Math.ceil(bounds[3] - bounds[1]), 1); + var offsetX = Math.floor(bounds[0]); + var offsetY = Math.floor(bounds[1]); + var drawnWidth = Math.max(Math.ceil(bounds[2]) - offsetX, 1); + var drawnHeight = Math.max(Math.ceil(bounds[3]) - offsetY, 1); + var scaleX = 1, scaleY = 1; + if (drawnWidth > MAX_GROUP_SIZE) { + scaleX = drawnWidth / MAX_GROUP_SIZE; + drawnWidth = MAX_GROUP_SIZE; + } + if (drawnHeight > MAX_GROUP_SIZE) { + scaleY = drawnHeight / MAX_GROUP_SIZE; + drawnHeight = MAX_GROUP_SIZE; + } var cacheId = 'groupAt' + this.groupLevel; if (group.smask) { @@ -6886,8 +5379,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { // Since we created a new canvas that is just the size of the bounding box // we have to translate the group ctx. - var offsetX = bounds[0]; - var offsetY = bounds[1]; + groupCtx.scale(1 / scaleX, 1 / scaleY); groupCtx.translate(-offsetX, -offsetY); groupCtx.transform.apply(groupCtx, currentTransform); @@ -6898,15 +5390,17 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { context: groupCtx, offsetX: offsetX, offsetY: offsetY, + scaleX: scaleX, + scaleY: scaleY, subtype: group.smask.subtype, - backdrop: group.smask.backdrop, - colorSpace: group.colorSpace && ColorSpace.fromIR(group.colorSpace) + backdrop: group.smask.backdrop }); } else { // Setup the current ctx so when the group is popped we draw it at the // right location. currentCtx.setTransform(1, 0, 0, 1, 0, 0); currentCtx.translate(offsetX, offsetY); + currentCtx.scale(scaleX, scaleY); } // The transparency group inherits all off the current graphics state // except the blend mode, soft mask, and alpha constants. @@ -6927,7 +5421,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { this.ctx = this.groupStack.pop(); // Turn off image smoothing to avoid sub pixel interpolation which can // look kind of blurry for some pdfs. - if ('imageSmoothingEnabled' in this.ctx) { + if (this.ctx.imageSmoothingEnabled !== undefined) { this.ctx.imageSmoothingEnabled = false; } else { this.ctx.mozImageSmoothingEnabled = false; @@ -6972,7 +5466,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { paintJpegXObject: function CanvasGraphics_paintJpegXObject(objId, w, h) { var domImage = this.objs.get(objId); if (!domImage) { - error('Dependent image isn\'t ready yet'); + warn('Dependent image isn\'t ready yet'); + return; } this.save(); @@ -7106,7 +5601,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { paintImageXObject: function CanvasGraphics_paintImageXObject(objId) { var imgData = this.objs.get(objId); if (!imgData) { - error('Dependent image isn\'t ready yet'); + warn('Dependent image isn\'t ready yet'); + return; } this.paintInlineImageXObject(imgData); @@ -7117,7 +5613,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { positions) { var imgData = this.objs.get(objId); if (!imgData) { - error('Dependent image isn\'t ready yet'); + warn('Dependent image isn\'t ready yet'); + return; } var width = imgData.width; @@ -7146,12 +5643,12 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { var c = currentTransform[2], d = currentTransform[3]; var heightScale = Math.max(Math.sqrt(c * c + d * d), 1); - var imgToPaint; + var imgToPaint, tmpCanvas; // instanceof HTMLElement does not work in jsdom node.js module if (imgData instanceof HTMLElement || !imgData.data) { imgToPaint = imgData; } else { - var tmpCanvas = CachedCanvases.getCanvas('inlineImage', width, height); + tmpCanvas = CachedCanvases.getCanvas('inlineImage', width, height); var tmpCtx = tmpCanvas.context; putBinaryImageData(tmpCtx, imgData); imgToPaint = tmpCanvas.canvas; @@ -7173,8 +5670,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { newHeight = Math.ceil(paintHeight / 2); heightScale /= paintHeight / newHeight; } - var tmpCanvas = CachedCanvases.getCanvas(tmpCanvasId, - newWidth, newHeight); + tmpCanvas = CachedCanvases.getCanvas(tmpCanvasId, newWidth, newHeight); tmpCtx = tmpCanvas.context; tmpCtx.clearRect(0, 0, newWidth, newHeight); tmpCtx.drawImage(imgToPaint, 0, 0, paintWidth, paintHeight, @@ -7231,6 +5727,11 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { } }, + paintSolidColorImageMask: + function CanvasGraphics_paintSolidColorImageMask() { + this.ctx.fillRect(0, 0, 1, 1); + }, + // Marked content markPoint: function CanvasGraphics_markPoint(tag) { @@ -7262,26 +5763,27 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { // Helper functions consumePath: function CanvasGraphics_consumePath() { + var ctx = this.ctx; if (this.pendingClip) { if (this.pendingClip == EO_CLIP) { - if ('mozFillRule' in this.ctx) { - this.ctx.mozFillRule = 'evenodd'; - this.ctx.clip(); - this.ctx.mozFillRule = 'nonzero'; + if (ctx.mozFillRule !== undefined) { + ctx.mozFillRule = 'evenodd'; + ctx.clip(); + ctx.mozFillRule = 'nonzero'; } else { try { - this.ctx.clip('evenodd'); + ctx.clip('evenodd'); } catch (ex) { // shouldn't really happen, but browsers might think differently - this.ctx.clip(); + ctx.clip(); } } } else { - this.ctx.clip(); + ctx.clip(); } this.pendingClip = null; } - this.ctx.beginPath(); + ctx.beginPath(); }, getSinglePixelWidth: function CanvasGraphics_getSinglePixelWidth(scale) { var inverse = this.ctx.mozCurrentTransformInverse; @@ -7308,6 +5810,416 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { +var WebGLUtils = (function WebGLUtilsClosure() { + function loadShader(gl, code, shaderType) { + var shader = gl.createShader(shaderType); + gl.shaderSource(shader, code); + gl.compileShader(shader); + var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS); + if (!compiled) { + var errorMsg = gl.getShaderInfoLog(shader); + throw new Error('Error during shader compilation: ' + errorMsg); + } + return shader; + } + function createVertexShader(gl, code) { + return loadShader(gl, code, gl.VERTEX_SHADER); + } + function createFragmentShader(gl, code) { + return loadShader(gl, code, gl.FRAGMENT_SHADER); + } + function createProgram(gl, shaders) { + var program = gl.createProgram(); + for (var i = 0, ii = shaders.length; i < ii; ++i) { + gl.attachShader(program, shaders[i]); + } + gl.linkProgram(program); + var linked = gl.getProgramParameter(program, gl.LINK_STATUS); + if (!linked) { + var errorMsg = gl.getProgramInfoLog(program); + throw new Error('Error during program linking: ' + errorMsg); + } + return program; + } + function createTexture(gl, image, textureId) { + gl.activeTexture(textureId); + var texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + + // Set the parameters so we can render any size image. + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + + // Upload the image into the texture. + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); + return texture; + } + + var currentGL, currentCanvas; + function generageGL() { + if (currentGL) { + return; + } + currentCanvas = document.createElement('canvas'); + currentGL = currentCanvas.getContext('webgl', + { premultipliedalpha: false }); + } + + var smaskVertexShaderCode = '\ + attribute vec2 a_position; \ + attribute vec2 a_texCoord; \ + \ + uniform vec2 u_resolution; \ + \ + varying vec2 v_texCoord; \ + \ + void main() { \ + vec2 clipSpace = (a_position / u_resolution) * 2.0 - 1.0; \ + gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1); \ + \ + v_texCoord = a_texCoord; \ + } '; + + var smaskFragmentShaderCode = '\ + precision mediump float; \ + \ + uniform vec4 u_backdrop; \ + uniform int u_subtype; \ + uniform sampler2D u_image; \ + uniform sampler2D u_mask; \ + \ + varying vec2 v_texCoord; \ + \ + void main() { \ + vec4 imageColor = texture2D(u_image, v_texCoord); \ + vec4 maskColor = texture2D(u_mask, v_texCoord); \ + if (u_backdrop.a > 0.0) { \ + maskColor.rgb = maskColor.rgb * maskColor.a + \ + u_backdrop.rgb * (1.0 - maskColor.a); \ + } \ + float lum; \ + if (u_subtype == 0) { \ + lum = maskColor.a; \ + } else { \ + lum = maskColor.r * 0.3 + maskColor.g * 0.59 + \ + maskColor.b * 0.11; \ + } \ + imageColor.a *= lum; \ + imageColor.rgb *= imageColor.a; \ + gl_FragColor = imageColor; \ + } '; + + var smaskCache = null; + + function initSmaskGL() { + var canvas, gl; + + generageGL(); + canvas = currentCanvas; + currentCanvas = null; + gl = currentGL; + currentGL = null; + + // setup a GLSL program + var vertexShader = createVertexShader(gl, smaskVertexShaderCode); + var fragmentShader = createFragmentShader(gl, smaskFragmentShaderCode); + var program = createProgram(gl, [vertexShader, fragmentShader]); + gl.useProgram(program); + + var cache = {}; + cache.gl = gl; + cache.canvas = canvas; + cache.resolutionLocation = gl.getUniformLocation(program, 'u_resolution'); + cache.positionLocation = gl.getAttribLocation(program, 'a_position'); + cache.backdropLocation = gl.getUniformLocation(program, 'u_backdrop'); + cache.subtypeLocation = gl.getUniformLocation(program, 'u_subtype'); + + var texCoordLocation = gl.getAttribLocation(program, 'a_texCoord'); + var texLayerLocation = gl.getUniformLocation(program, 'u_image'); + var texMaskLocation = gl.getUniformLocation(program, 'u_mask'); + + // provide texture coordinates for the rectangle. + var texCoordBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer); + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ + 0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 0.0, 1.0, + 1.0, 0.0, + 1.0, 1.0]), gl.STATIC_DRAW); + gl.enableVertexAttribArray(texCoordLocation); + gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0); + + gl.uniform1i(texLayerLocation, 0); + gl.uniform1i(texMaskLocation, 1); + + smaskCache = cache; + } + + function composeSMask(layer, mask, properties) { + var width = layer.width, height = layer.height; + + if (!smaskCache) { + initSmaskGL(); + } + var cache = smaskCache,canvas = cache.canvas, gl = cache.gl; + canvas.width = width; + canvas.height = height; + gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); + gl.uniform2f(cache.resolutionLocation, width, height); + + if (properties.backdrop) { + gl.uniform4f(cache.resolutionLocation, properties.backdrop[0], + properties.backdrop[1], properties.backdrop[2], 1); + } else { + gl.uniform4f(cache.resolutionLocation, 0, 0, 0, 0); + } + gl.uniform1i(cache.subtypeLocation, + properties.subtype === 'Luminosity' ? 1 : 0); + + // Create a textures + var texture = createTexture(gl, layer, gl.TEXTURE0); + var maskTexture = createTexture(gl, mask, gl.TEXTURE1); + + + // Create a buffer and put a single clipspace rectangle in + // it (2 triangles) + var buffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ + 0, 0, + width, 0, + 0, height, + 0, height, + width, 0, + width, height]), gl.STATIC_DRAW); + gl.enableVertexAttribArray(cache.positionLocation); + gl.vertexAttribPointer(cache.positionLocation, 2, gl.FLOAT, false, 0, 0); + + // draw + gl.clearColor(0, 0, 0, 0); + gl.enable(gl.BLEND); + gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + gl.clear(gl.COLOR_BUFFER_BIT); + + gl.drawArrays(gl.TRIANGLES, 0, 6); + + gl.flush(); + + gl.deleteTexture(texture); + gl.deleteTexture(maskTexture); + gl.deleteBuffer(buffer); + + return canvas; + } + + var figuresVertexShaderCode = '\ + attribute vec2 a_position; \ + attribute vec3 a_color; \ + \ + uniform vec2 u_resolution; \ + uniform vec2 u_scale; \ + uniform vec2 u_offset; \ + \ + varying vec4 v_color; \ + \ + void main() { \ + vec2 position = (a_position + u_offset) * u_scale; \ + vec2 clipSpace = (position / u_resolution) * 2.0 - 1.0; \ + gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1); \ + \ + v_color = vec4(a_color / 255.0, 1.0); \ + } '; + + var figuresFragmentShaderCode = '\ + precision mediump float; \ + \ + varying vec4 v_color; \ + \ + void main() { \ + gl_FragColor = v_color; \ + } '; + + var figuresCache = null; + + function initFiguresGL() { + var canvas, gl; + + generageGL(); + canvas = currentCanvas; + currentCanvas = null; + gl = currentGL; + currentGL = null; + + // setup a GLSL program + var vertexShader = createVertexShader(gl, figuresVertexShaderCode); + var fragmentShader = createFragmentShader(gl, figuresFragmentShaderCode); + var program = createProgram(gl, [vertexShader, fragmentShader]); + gl.useProgram(program); + + var cache = {}; + cache.gl = gl; + cache.canvas = canvas; + cache.resolutionLocation = gl.getUniformLocation(program, 'u_resolution'); + cache.scaleLocation = gl.getUniformLocation(program, 'u_scale'); + cache.offsetLocation = gl.getUniformLocation(program, 'u_offset'); + cache.positionLocation = gl.getAttribLocation(program, 'a_position'); + cache.colorLocation = gl.getAttribLocation(program, 'a_color'); + + figuresCache = cache; + } + + function drawFigures(width, height, backgroundColor, figures, context) { + if (!figuresCache) { + initFiguresGL(); + } + var cache = figuresCache, canvas = cache.canvas, gl = cache.gl; + + canvas.width = width; + canvas.height = height; + gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); + gl.uniform2f(cache.resolutionLocation, width, height); + + // count triangle points + var count = 0; + var i, ii, rows; + for (i = 0, ii = figures.length; i < ii; i++) { + switch (figures[i].type) { + case 'lattice': + rows = (figures[i].coords.length / figures[i].verticesPerRow) | 0; + count += (rows - 1) * (figures[i].verticesPerRow - 1) * 6; + break; + case 'triangles': + count += figures[i].coords.length; + break; + } + } + // transfer data + var coords = new Float32Array(count * 2); + var colors = new Uint8Array(count * 3); + var coordsMap = context.coords, colorsMap = context.colors; + var pIndex = 0, cIndex = 0; + for (i = 0, ii = figures.length; i < ii; i++) { + var figure = figures[i], ps = figure.coords, cs = figure.colors; + switch (figure.type) { + case 'lattice': + var cols = figure.verticesPerRow; + rows = (ps.length / cols) | 0; + for (var row = 1; row < rows; row++) { + var offset = row * cols + 1; + for (var col = 1; col < cols; col++, offset++) { + coords[pIndex] = coordsMap[ps[offset - cols - 1]]; + coords[pIndex + 1] = coordsMap[ps[offset - cols - 1] + 1]; + coords[pIndex + 2] = coordsMap[ps[offset - cols]]; + coords[pIndex + 3] = coordsMap[ps[offset - cols] + 1]; + coords[pIndex + 4] = coordsMap[ps[offset - 1]]; + coords[pIndex + 5] = coordsMap[ps[offset - 1] + 1]; + colors[cIndex] = colorsMap[cs[offset - cols - 1]]; + colors[cIndex + 1] = colorsMap[cs[offset - cols - 1] + 1]; + colors[cIndex + 2] = colorsMap[cs[offset - cols - 1] + 2]; + colors[cIndex + 3] = colorsMap[cs[offset - cols]]; + colors[cIndex + 4] = colorsMap[cs[offset - cols] + 1]; + colors[cIndex + 5] = colorsMap[cs[offset - cols] + 2]; + colors[cIndex + 6] = colorsMap[cs[offset - 1]]; + colors[cIndex + 7] = colorsMap[cs[offset - 1] + 1]; + colors[cIndex + 8] = colorsMap[cs[offset - 1] + 2]; + + coords[pIndex + 6] = coords[pIndex + 2]; + coords[pIndex + 7] = coords[pIndex + 3]; + coords[pIndex + 8] = coords[pIndex + 4]; + coords[pIndex + 9] = coords[pIndex + 5]; + coords[pIndex + 10] = coordsMap[ps[offset]]; + coords[pIndex + 11] = coordsMap[ps[offset] + 1]; + colors[cIndex + 9] = colors[cIndex + 3]; + colors[cIndex + 10] = colors[cIndex + 4]; + colors[cIndex + 11] = colors[cIndex + 5]; + colors[cIndex + 12] = colors[cIndex + 6]; + colors[cIndex + 13] = colors[cIndex + 7]; + colors[cIndex + 14] = colors[cIndex + 8]; + colors[cIndex + 15] = colorsMap[cs[offset]]; + colors[cIndex + 16] = colorsMap[cs[offset] + 1]; + colors[cIndex + 17] = colorsMap[cs[offset] + 2]; + pIndex += 12; + cIndex += 18; + } + } + break; + case 'triangles': + for (var j = 0, jj = ps.length; j < jj; j++) { + coords[pIndex] = coordsMap[ps[j]]; + coords[pIndex + 1] = coordsMap[ps[j] + 1]; + colors[cIndex] = colorsMap[cs[i]]; + colors[cIndex + 1] = colorsMap[cs[j] + 1]; + colors[cIndex + 2] = colorsMap[cs[j] + 2]; + pIndex += 2; + cIndex += 3; + } + break; + } + } + + // draw + if (backgroundColor) { + gl.clearColor(backgroundColor[0] / 255, backgroundColor[1] / 255, + backgroundColor[2] / 255, 1.0); + } else { + gl.clearColor(0, 0, 0, 0); + } + gl.clear(gl.COLOR_BUFFER_BIT); + + var coordsBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, coordsBuffer); + gl.bufferData(gl.ARRAY_BUFFER, coords, gl.STATIC_DRAW); + gl.enableVertexAttribArray(cache.positionLocation); + gl.vertexAttribPointer(cache.positionLocation, 2, gl.FLOAT, false, 0, 0); + + var colorsBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, colorsBuffer); + gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW); + gl.enableVertexAttribArray(cache.colorLocation); + gl.vertexAttribPointer(cache.colorLocation, 3, gl.UNSIGNED_BYTE, false, + 0, 0); + + gl.uniform2f(cache.scaleLocation, context.scaleX, context.scaleY); + gl.uniform2f(cache.offsetLocation, context.offsetX, context.offsetY); + + gl.drawArrays(gl.TRIANGLES, 0, count); + + gl.flush(); + + gl.deleteBuffer(coordsBuffer); + gl.deleteBuffer(colorsBuffer); + + return canvas; + } + + function cleanup() { + smaskCache = null; + figuresCache = null; + } + + return { + get isEnabled() { + if (PDFJS.disableWebGL) { + return false; + } + var enabled = false; + try { + generageGL(); + enabled = !!currentGL; + } catch (e) { } + return shadow(this, 'isEnabled', enabled); + }, + composeSMask: composeSMask, + drawFigures: drawFigures, + clear: cleanup + }; +})(); + + var ShadingIRs = {}; ShadingIRs.RadialAxial = { @@ -7344,28 +6256,27 @@ var createMeshCanvas = (function createMeshCanvasClosure() { var coords = context.coords, colors = context.colors; var bytes = data.data, rowSize = data.width * 4; var tmp; - if (coords[p1 * 2 + 1] > coords[p2 * 2 + 1]) { + if (coords[p1 + 1] > coords[p2 + 1]) { tmp = p1; p1 = p2; p2 = tmp; tmp = c1; c1 = c2; c2 = tmp; } - if (coords[p2 * 2 + 1] > coords[p3 * 2 + 1]) { + if (coords[p2 + 1] > coords[p3 + 1]) { tmp = p2; p2 = p3; p3 = tmp; tmp = c2; c2 = c3; c3 = tmp; } - if (coords[p1 * 2 + 1] > coords[p2 * 2 + 1]) { + if (coords[p1 + 1] > coords[p2 + 1]) { tmp = p1; p1 = p2; p2 = tmp; tmp = c1; c1 = c2; c2 = tmp; } - var x1 = (coords[p1 * 2] + context.offsetX) * context.scaleX; - var y1 = (coords[p1 * 2 + 1] + context.offsetY) * context.scaleY; - var x2 = (coords[p2 * 2] + context.offsetX) * context.scaleX; - var y2 = (coords[p2 * 2 + 1] + context.offsetY) * context.scaleY; - var x3 = (coords[p3 * 2] + context.offsetX) * context.scaleX; - var y3 = (coords[p3 * 2 + 1] + context.offsetY) * context.scaleY; + var x1 = (coords[p1] + context.offsetX) * context.scaleX; + var y1 = (coords[p1 + 1] + context.offsetY) * context.scaleY; + var x2 = (coords[p2] + context.offsetX) * context.scaleX; + var y2 = (coords[p2 + 1] + context.offsetY) * context.scaleY; + var x3 = (coords[p3] + context.offsetX) * context.scaleX; + var y3 = (coords[p3 + 1] + context.offsetY) * context.scaleY; if (y1 >= y3) { return; } - var c1i = c1 * 3, c2i = c2 * 3, c3i = c3 * 3; - var c1r = colors[c1i], c1g = colors[c1i + 1], c1b = colors[c1i + 2]; - var c2r = colors[c2i], c2g = colors[c2i + 1], c2b = colors[c2i + 2]; - var c3r = colors[c3i], c3g = colors[c3i + 1], c3b = colors[c3i + 2]; + var c1r = colors[c1], c1g = colors[c1 + 1], c1b = colors[c1 + 2]; + var c2r = colors[c2], c2g = colors[c2 + 1], c2b = colors[c2 + 2]; + var c3r = colors[c3], c3g = colors[c3 + 1], c3b = colors[c3 + 2]; var minY = Math.round(y1), maxY = Math.round(y3); var xa, car, cag, cab; @@ -7407,12 +6318,13 @@ var createMeshCanvas = (function createMeshCanvasClosure() { function drawFigure(data, figure, context) { var ps = figure.coords; var cs = figure.colors; + var i, ii; switch (figure.type) { case 'lattice': var verticesPerRow = figure.verticesPerRow; var rows = Math.floor(ps.length / verticesPerRow) - 1; var cols = verticesPerRow - 1; - for (var i = 0; i < rows; i++) { + for (i = 0; i < rows; i++) { var q = i * verticesPerRow; for (var j = 0; j < cols; j++, q++) { drawTriangle(data, context, @@ -7425,7 +6337,7 @@ var createMeshCanvas = (function createMeshCanvasClosure() { } break; case 'triangles': - for (var i = 0, ii = ps.length; i < ii; i += 3) { + for (i = 0, ii = ps.length; i < ii; i += 3) { drawTriangle(data, context, ps[i], ps[i + 1], ps[i + 2], cs[i], cs[i + 1], cs[i + 2]); @@ -7445,52 +6357,72 @@ var createMeshCanvas = (function createMeshCanvasClosure() { // MAX_PATTERN_SIZE is used to avoid OOM situation. var MAX_PATTERN_SIZE = 3000; // 10in @ 300dpi shall be enough - var boundsWidth = bounds[2] - bounds[0]; - var boundsHeight = bounds[3] - bounds[1]; + var offsetX = Math.floor(bounds[0]); + var offsetY = Math.floor(bounds[1]); + var boundsWidth = Math.ceil(bounds[2]) - offsetX; + var boundsHeight = Math.ceil(bounds[3]) - offsetY; var width = Math.min(Math.ceil(Math.abs(boundsWidth * combinesScale[0] * EXPECTED_SCALE)), MAX_PATTERN_SIZE); var height = Math.min(Math.ceil(Math.abs(boundsHeight * combinesScale[1] * EXPECTED_SCALE)), MAX_PATTERN_SIZE); - var scaleX = width / boundsWidth; - var scaleY = height / boundsHeight; - - var tmpCanvas = CachedCanvases.getCanvas('mesh', width, height, false); - var tmpCtx = tmpCanvas.context; - if (backgroundColor) { - tmpCtx.fillStyle = makeCssRgb(backgroundColor); - tmpCtx.fillRect(0, 0, width, height); - } + var scaleX = boundsWidth / width; + var scaleY = boundsHeight / height; var context = { coords: coords, colors: colors, - offsetX: -bounds[0], - offsetY: -bounds[1], - scaleX: scaleX, - scaleY: scaleY + offsetX: -offsetX, + offsetY: -offsetY, + scaleX: 1 / scaleX, + scaleY: 1 / scaleY }; - var data = tmpCtx.getImageData(0, 0, width, height); - for (var i = 0; i < figures.length; i++) { - drawFigure(data, figures[i], context); + var canvas, tmpCanvas, i, ii; + if (WebGLUtils.isEnabled) { + canvas = WebGLUtils.drawFigures(width, height, backgroundColor, + figures, context); + + // https://bugzilla.mozilla.org/show_bug.cgi?id=972126 + tmpCanvas = CachedCanvases.getCanvas('mesh', width, height, false); + tmpCanvas.context.drawImage(canvas, 0, 0); + canvas = tmpCanvas.canvas; + } else { + tmpCanvas = CachedCanvases.getCanvas('mesh', width, height, false); + var tmpCtx = tmpCanvas.context; + + var data = tmpCtx.createImageData(width, height); + if (backgroundColor) { + var bytes = data.data; + for (i = 0, ii = bytes.length; i < ii; i += 4) { + bytes[i] = backgroundColor[0]; + bytes[i + 1] = backgroundColor[1]; + bytes[i + 2] = backgroundColor[2]; + bytes[i + 3] = 255; + } + } + for (i = 0; i < figures.length; i++) { + drawFigure(data, figures[i], context); + } + tmpCtx.putImageData(data, 0, 0); + canvas = tmpCanvas.canvas; } - tmpCtx.putImageData(data, 0, 0); - return {canvas: tmpCanvas.canvas, scaleX: 1 / scaleX, scaleY: 1 / scaleY}; + return {canvas: canvas, offsetX: offsetX, offsetY: offsetY, + scaleX: scaleX, scaleY: scaleY}; } return createMeshCanvas; })(); ShadingIRs.Mesh = { fromIR: function Mesh_fromIR(raw) { - var type = raw[1]; + //var type = raw[1]; var coords = raw[2]; var colors = raw[3]; var figures = raw[4]; var bounds = raw[5]; var matrix = raw[6]; - var bbox = raw[7]; + //var bbox = raw[7]; var background = raw[8]; return { type: 'Pattern', @@ -7511,7 +6443,6 @@ ShadingIRs.Mesh = { // Rasterizing on the main thread since sending/queue large canvases // might cause OOM. - // TODO consider using WebGL or asm.js to perform rasterization var temporaryPatternCanvas = createMeshCanvas(bounds, combinedScale, coords, colors, figures, shadingFill ? null : background); @@ -7522,7 +6453,8 @@ ShadingIRs.Mesh = { } } - ctx.translate(bounds[0], bounds[1]); + ctx.translate(temporaryPatternCanvas.offsetX, + temporaryPatternCanvas.offsetY); ctx.scale(temporaryPatternCanvas.scaleX, temporaryPatternCanvas.scaleY); @@ -7560,7 +6492,6 @@ var TilingPattern = (function TilingPatternClosure() { var MAX_PATTERN_SIZE = 3000; // 10in @ 300dpi shall be enough function TilingPattern(IR, color, ctx, objs, commonObjs, baseTransform) { - this.name = IR[1][0].name; this.operatorList = IR[2]; this.matrix = IR[3] || [1, 0, 0, 1, 0, 0]; this.bbox = IR[4]; @@ -7587,7 +6518,6 @@ var TilingPattern = (function TilingPatternClosure() { var color = this.color; var objs = this.objs; var commonObjs = this.commonObjs; - var ctx = this.ctx; info('TilingType: ' + tilingType); @@ -7671,8 +6601,7 @@ var TilingPattern = (function TilingPatternClosure() { context.strokeStyle = ctx.strokeStyle; break; case PaintType.UNCOLORED: - var rgbColor = ColorSpace.singletons.rgb.getRgb(color, 0); - var cssColor = Util.makeCssRgb(rgbColor); + var cssColor = Util.makeCssRgb(color); context.fillStyle = cssColor; context.strokeStyle = cssColor; break; @@ -7684,7 +6613,7 @@ var TilingPattern = (function TilingPatternClosure() { getPattern: function TilingPattern_getPattern(ctx, owner) { var temporaryPatternCanvas = this.createPatternCanvas(owner); - var ctx = this.ctx; + ctx = this.ctx; ctx.setTransform.apply(ctx, this.baseTransform); ctx.transform.apply(ctx, this.matrix); this.scaleToContext(); @@ -7841,16 +6770,9 @@ var FontLoader = { (data.charCodeAt(offset + 3) & 0xff); } - function string32(value) { - return String.fromCharCode((value >> 24) & 0xff) + - String.fromCharCode((value >> 16) & 0xff) + - String.fromCharCode((value >> 8) & 0xff) + - String.fromCharCode(value & 0xff); - } - function spliceString(s, offset, remove, insert) { - var chunk1 = data.substr(0, offset); - var chunk2 = data.substr(offset + remove); + var chunk1 = s.substr(0, offset); + var chunk2 = s.substr(offset + remove); return chunk1 + insert + chunk2; } @@ -7958,7 +6880,7 @@ var FontFace = (function FontFaceClosure() { return null; } - var data = bytesToString(this.data); + var data = bytesToString(new Uint8Array(this.data)); var fontName = this.loadedName; // Add the font-face rule to the document diff --git a/js/pdf.worker.js b/js/pdf.worker.js index 23c1805..a7145d7 100644 --- a/js/pdf.worker.js +++ b/js/pdf.worker.js @@ -21,8 +21,8 @@ if (typeof PDFJS === 'undefined') { (typeof window !== 'undefined' ? window : this).PDFJS = {}; } -PDFJS.version = '0.8.1239'; -PDFJS.build = '1a6e103'; +PDFJS.version = '1.0.276'; +PDFJS.build = 'a09aecb'; (function pdfjsWrapper() { // Use strict in our context only - users might not want it @@ -181,7 +181,9 @@ var OPS = PDFJS.OPS = { paintInlineImageXObject: 86, paintInlineImageXObjectGroup: 87, paintImageXObjectRepeat: 88, - paintImageMaskXObjectRepeat: 89 + paintImageMaskXObjectRepeat: 89, + paintSolidColorImageMask: 90, + constructPath: 91 }; // A notice for devs. These are good for things that are helpful to devs, such @@ -266,9 +268,10 @@ function combineUrl(baseUrl, url) { if (/^[a-z][a-z0-9+\-.]*:/i.test(url)) { return url; } + var i; if (url.charAt(0) == '/') { // absolute path - var i = baseUrl.indexOf('://'); + i = baseUrl.indexOf('://'); if (url.charAt(1) === '/') { ++i; } else { @@ -277,7 +280,7 @@ function combineUrl(baseUrl, url) { return baseUrl.substring(0, i) + url; } else { // relative path - var pathLength = baseUrl.length, i; + var pathLength = baseUrl.length; i = baseUrl.lastIndexOf('#'); pathLength = i >= 0 ? i : pathLength; i = baseUrl.lastIndexOf('?', pathLength); @@ -311,14 +314,6 @@ function isValidUrl(url, allowRelative) { } PDFJS.isValidUrl = isValidUrl; -// In a well-formed PDF, |cond| holds. If it doesn't, subsequent -// behavior is undefined. -function assertWellFormed(cond, msg) { - if (!cond) { - error(msg); - } -} - function shadow(obj, prop, value) { Object.defineProperty(obj, prop, { value: value, enumerable: true, @@ -422,23 +417,137 @@ var XRefParseException = (function XRefParseExceptionClosure() { function bytesToString(bytes) { - var strBuf = []; var length = bytes.length; - for (var n = 0; n < length; ++n) { - strBuf.push(String.fromCharCode(bytes[n])); + var MAX_ARGUMENT_COUNT = 8192; + if (length < MAX_ARGUMENT_COUNT) { + return String.fromCharCode.apply(null, bytes); + } + var strBuf = []; + for (var i = 0; i < length; i += MAX_ARGUMENT_COUNT) { + var chunkEnd = Math.min(i + MAX_ARGUMENT_COUNT, length); + var chunk = bytes.subarray(i, chunkEnd); + strBuf.push(String.fromCharCode.apply(null, chunk)); } return strBuf.join(''); } +function stringToArray(str) { + var length = str.length; + var array = []; + for (var i = 0; i < length; ++i) { + array[i] = str.charCodeAt(i); + } + return array; +} + function stringToBytes(str) { var length = str.length; var bytes = new Uint8Array(length); - for (var n = 0; n < length; ++n) { - bytes[n] = str.charCodeAt(n) & 0xFF; + for (var i = 0; i < length; ++i) { + bytes[i] = str.charCodeAt(i) & 0xFF; } return bytes; } +function string32(value) { + return String.fromCharCode((value >> 24) & 0xff, (value >> 16) & 0xff, + (value >> 8) & 0xff, value & 0xff); +} + +function log2(x) { + var n = 1, i = 0; + while (x > n) { + n <<= 1; + i++; + } + return i; +} + +function readInt8(data, start) { + return (data[start] << 24) >> 24; +} + +function readUint16(data, offset) { + return (data[offset] << 8) | data[offset + 1]; +} + +function readUint32(data, offset) { + return ((data[offset] << 24) | (data[offset + 1] << 16) | + (data[offset + 2] << 8) | data[offset + 3]) >>> 0; +} + +// Lazy test the endianness of the platform +// NOTE: This will be 'true' for simulated TypedArrays +function isLittleEndian() { + var buffer8 = new Uint8Array(2); + buffer8[0] = 1; + var buffer16 = new Uint16Array(buffer8.buffer); + return (buffer16[0] === 1); +} + +Object.defineProperty(PDFJS, 'isLittleEndian', { + configurable: true, + get: function PDFJS_isLittleEndian() { + return shadow(PDFJS, 'isLittleEndian', isLittleEndian()); + } +}); + + // Lazy test if the userAgant support CanvasTypedArrays +function hasCanvasTypedArrays() { + var canvas = document.createElement('canvas'); + canvas.width = canvas.height = 1; + var ctx = canvas.getContext('2d'); + var imageData = ctx.createImageData(1, 1); + return (typeof imageData.data.buffer !== 'undefined'); +} + +Object.defineProperty(PDFJS, 'hasCanvasTypedArrays', { + configurable: true, + get: function PDFJS_hasCanvasTypedArrays() { + return shadow(PDFJS, 'hasCanvasTypedArrays', hasCanvasTypedArrays()); + } +}); + +var Uint32ArrayView = (function Uint32ArrayViewClosure() { + + function Uint32ArrayView(buffer, length) { + this.buffer = buffer; + this.byteLength = buffer.length; + this.length = length === undefined ? (this.byteLength >> 2) : length; + ensureUint32ArrayViewProps(this.length); + } + Uint32ArrayView.prototype = Object.create(null); + + var uint32ArrayViewSetters = 0; + function createUint32ArrayProp(index) { + return { + get: function () { + var buffer = this.buffer, offset = index << 2; + return (buffer[offset] | (buffer[offset + 1] << 8) | + (buffer[offset + 2] << 16) | (buffer[offset + 3] << 24)) >>> 0; + }, + set: function (value) { + var buffer = this.buffer, offset = index << 2; + buffer[offset] = value & 255; + buffer[offset + 1] = (value >> 8) & 255; + buffer[offset + 2] = (value >> 16) & 255; + buffer[offset + 3] = (value >>> 24) & 255; + } + }; + } + + function ensureUint32ArrayViewProps(length) { + while (uint32ArrayViewSetters < length) { + Object.defineProperty(Uint32ArrayView.prototype, + uint32ArrayViewSetters, + createUint32ArrayProp(uint32ArrayViewSetters)); + uint32ArrayViewSetters++; + } + } + + return Uint32ArrayView; +})(); + var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0]; var Util = PDFJS.Util = (function UtilClosure() { @@ -448,11 +557,6 @@ var Util = PDFJS.Util = (function UtilClosure() { return 'rgb(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ')'; }; - Util.makeCssCmyk = function Util_makeCssCmyk(cmyk) { - var rgb = ColorSpace.singletons.cmyk.getRgb(cmyk, 0); - return Util.makeCssRgb(rgb); - }; - // Concatenates two transformation matrices together and returns the result. Util.transform = function Util_transform(m1, m2) { return [ @@ -652,7 +756,22 @@ var Util = PDFJS.Util = (function UtilClosure() { return Util; })(); +/** + * PDF page viewport created based on scale, rotation and offset. + * @class + * @alias PDFJS.PageViewport + */ var PageViewport = PDFJS.PageViewport = (function PageViewportClosure() { + /** + * @constructor + * @private + * @param viewBox {Array} xMin, yMin, xMax and yMax coordinates. + * @param scale {number} scale of the viewport. + * @param rotation {number} rotations of the viewport in degrees. + * @param offsetX {number} offset X + * @param offsetY {number} offset Y + * @param dontFlip {boolean} if true, axis Y will not be flipped. + */ function PageViewport(viewBox, scale, rotation, offsetX, offsetY, dontFlip) { this.viewBox = viewBox; this.scale = scale; @@ -716,7 +835,14 @@ var PageViewport = PDFJS.PageViewport = (function PageViewportClosure() { this.height = height; this.fontScale = scale; } - PageViewport.prototype = { + PageViewport.prototype = /** @lends PDFJS.PageViewport.prototype */ { + /** + * Clones viewport with additional properties. + * @param args {Object} (optional) If specified, may contain the 'scale' or + * 'rotation' properties to override the corresponding properties in + * the cloned viewport. + * @returns {PDFJS.PageViewport} Cloned viewport. + */ clone: function PageViewPort_clone(args) { args = args || {}; var scale = 'scale' in args ? args.scale : this.scale; @@ -724,15 +850,41 @@ var PageViewport = PDFJS.PageViewport = (function PageViewportClosure() { return new PageViewport(this.viewBox.slice(), scale, rotation, this.offsetX, this.offsetY, args.dontFlip); }, + /** + * Converts PDF point to the viewport coordinates. For examples, useful for + * converting PDF location into canvas pixel coordinates. + * @param x {number} X coordinate. + * @param y {number} Y coordinate. + * @returns {Object} Object that contains 'x' and 'y' properties of the + * point in the viewport coordinate space. + * @see {@link convertToPdfPoint} + * @see {@link convertToViewportRectangle} + */ convertToViewportPoint: function PageViewport_convertToViewportPoint(x, y) { return Util.applyTransform([x, y], this.transform); }, + /** + * Converts PDF rectangle to the viewport coordinates. + * @param rect {Array} xMin, yMin, xMax and yMax coordinates. + * @returns {Array} Contains corresponding coordinates of the rectangle + * in the viewport coordinate space. + * @see {@link convertToViewportPoint} + */ convertToViewportRectangle: function PageViewport_convertToViewportRectangle(rect) { var tl = Util.applyTransform([rect[0], rect[1]], this.transform); var br = Util.applyTransform([rect[2], rect[3]], this.transform); return [tl[0], tl[1], br[0], br[1]]; }, + /** + * Converts viewport coordinates to the PDF location. For examples, useful + * for converting canvas pixel location into PDF one. + * @param x {number} X coordinate. + * @param y {number} Y coordinate. + * @returns {Object} Object that contains 'x' and 'y' properties of the + * point in the PDF coordinate space. + * @see {@link convertToViewportPoint} + */ convertToPdfPoint: function PageViewport_convertToPdfPoint(x, y) { return Util.applyInverseTransform([x, y], this.transform); } @@ -837,40 +989,36 @@ function isRef(v) { return v instanceof Ref; } -function isPDFFunction(v) { - var fnDict; - if (typeof v != 'object') { - return false; - } else if (isDict(v)) { - fnDict = v; - } else if (isStream(v)) { - fnDict = v.dict; - } else { - return false; - } - return fnDict.has('FunctionType'); -} +/** + * Promise Capability object. + * + * @typedef {Object} PromiseCapability + * @property {Promise} promise - A promise object. + * @property {function} resolve - Fullfills the promise. + * @property {function} reject - Rejects the promise. + */ /** - * Legacy support for PDFJS Promise implementation. - * TODO remove eventually + * Creates a promise capability object. + * @alias PDFJS.createPromiseCapability + * + * @return {PromiseCapability} A capability object contains: + * - a Promise, resolve and reject methods. */ -var LegacyPromise = PDFJS.LegacyPromise = (function LegacyPromiseClosure() { - return function LegacyPromise() { - var resolve, reject; - var promise = new Promise(function (resolve_, reject_) { - resolve = resolve_; - reject = reject_; - }); - promise.resolve = resolve; - promise.reject = reject; - return promise; - }; -})(); +function createPromiseCapability() { + var capability = {}; + capability.promise = new Promise(function (resolve, reject) { + capability.resolve = resolve; + capability.reject = reject; + }); + return capability; +} + +PDFJS.createPromiseCapability = createPromiseCapability; /** * Polyfill for Promises: - * The following promise implementation tries to generally implment the + * The following promise implementation tries to generally implement the * Promise/A+ spec. Some notable differences from other promise libaries are: * - There currently isn't a seperate deferred and promise object. * - Unhandled rejections eventually show an error if they aren't handled. @@ -905,8 +1053,20 @@ var LegacyPromise = PDFJS.LegacyPromise = (function LegacyPromiseClosure() { }; } if (typeof globalScope.Promise.resolve !== 'function') { - globalScope.Promise.resolve = function (x) { - return new globalScope.Promise(function (resolve) { resolve(x); }); + globalScope.Promise.resolve = function (value) { + return new globalScope.Promise(function (resolve) { resolve(value); }); + }; + } + if (typeof globalScope.Promise.reject !== 'function') { + globalScope.Promise.reject = function (reason) { + return new globalScope.Promise(function (resolve, reject) { + reject(reason); + }); + }; + } + if (typeof globalScope.Promise.prototype.catch !== 'function') { + globalScope.Promise.prototype.catch = function (onReject) { + return globalScope.Promise.prototype.then(undefined, onReject); }; } return; @@ -1031,7 +1191,11 @@ var LegacyPromise = PDFJS.LegacyPromise = (function LegacyPromiseClosure() { function Promise(resolver) { this._status = STATUS_PENDING; this._handlers = []; - resolver.call(this, this._resolve.bind(this), this._reject.bind(this)); + try { + resolver.call(this, this._resolve.bind(this), this._reject.bind(this)); + } catch (e) { + this._reject(e); + } } /** * Builds a promise that is resolved when all the passed in promises are @@ -1083,18 +1247,28 @@ var LegacyPromise = PDFJS.LegacyPromise = (function LegacyPromiseClosure() { /** * Checks if the value is likely a promise (has a 'then' function). - * @return {boolean} true if x is thenable + * @return {boolean} true if value is thenable */ Promise.isPromise = function Promise_isPromise(value) { return value && typeof value.then === 'function'; }; + /** * Creates resolved promise - * @param x resolve value + * @param value resolve value * @returns {Promise} */ - Promise.resolve = function Promise_resolve(x) { - return new Promise(function (resolve) { resolve(x); }); + Promise.resolve = function Promise_resolve(value) { + return new Promise(function (resolve) { resolve(value); }); + }; + + /** + * Creates rejected promise + * @param reason rejection value + * @returns {Promise} + */ + Promise.reject = function Promise_reject(reason) { + return new Promise(function (resolve, reject) { reject(reason); }); }; Promise.prototype = { @@ -1148,6 +1322,10 @@ var LegacyPromise = PDFJS.LegacyPromise = (function LegacyPromiseClosure() { }); HandlerManager.scheduleHandlers(this); return nextPromise; + }, + + catch: function Promise_catch(onReject) { + return this.then(undefined, onReject); } }; @@ -1192,17 +1370,18 @@ var StatTimer = (function StatTimerClosure() { delete this.started[name]; }, toString: function StatTimer_toString() { + var i, ii; var times = this.times; var out = ''; // Find the longest name for padding purposes. var longest = 0; - for (var i = 0, ii = times.length; i < ii; ++i) { + for (i = 0, ii = times.length; i < ii; ++i) { var name = times[i]['name']; if (name.length > longest) { longest = name.length; } } - for (var i = 0, ii = times.length; i < ii; ++i) { + for (i = 0, ii = times.length; i < ii; ++i) { var span = times[i]; var duration = span.end - span.start; out += rpad(span['name'], ' ', longest) + ' ' + duration + 'ms\n'; @@ -1254,7 +1433,7 @@ function MessageHandler(name, comObj) { this.comObj = comObj; this.callbackIndex = 1; this.postMessageTransfers = true; - var callbacks = this.callbacks = {}; + var callbacksCapabilities = this.callbacksCapabilities = {}; var ah = this.actionHandler = {}; ah['console_log'] = [function ahConsoleLog(data) { @@ -1271,35 +1450,40 @@ function MessageHandler(name, comObj) { var data = event.data; if (data.isReply) { var callbackId = data.callbackId; - if (data.callbackId in callbacks) { - var callback = callbacks[callbackId]; - delete callbacks[callbackId]; - callback(data.data); + if (data.callbackId in callbacksCapabilities) { + var callback = callbacksCapabilities[callbackId]; + delete callbacksCapabilities[callbackId]; + if ('error' in data) { + callback.reject(data.error); + } else { + callback.resolve(data.data); + } } else { error('Cannot resolve callback ' + callbackId); } } else if (data.action in ah) { var action = ah[data.action]; if (data.callbackId) { - var deferred = {}; - var promise = new Promise(function (resolve, reject) { - deferred.resolve = resolve; - deferred.reject = reject; - }); - deferred.promise = promise; - promise.then(function(resolvedData) { + Promise.resolve().then(function () { + return action[0].call(action[1], data.data); + }).then(function (result) { comObj.postMessage({ isReply: true, callbackId: data.callbackId, - data: resolvedData + data: result + }); + }, function (reason) { + comObj.postMessage({ + isReply: true, + callbackId: data.callbackId, + error: reason }); }); - action[0].call(action[1], data.data, deferred); } else { action[0].call(action[1], data.data); } } else { - error('Unkown action from worker: ' + data.action); + error('Unknown action from worker: ' + data.action); } }; } @@ -1316,19 +1500,47 @@ MessageHandler.prototype = { * Sends a message to the comObj to invoke the action with the supplied data. * @param {String} actionName Action to call. * @param {JSON} data JSON data to send. - * @param {function} [callback] Optional callback that will handle a reply. * @param {Array} [transfers] Optional list of transfers/ArrayBuffers */ - send: function messageHandlerSend(actionName, data, callback, transfers) { + send: function messageHandlerSend(actionName, data, transfers) { var message = { action: actionName, data: data }; - if (callback) { - var callbackId = this.callbackIndex++; - this.callbacks[callbackId] = callback; - message.callbackId = callbackId; + this.postMessage(message, transfers); + }, + /** + * Sends a message to the comObj to invoke the action with the supplied data. + * Expects that other side will callback with the response. + * @param {String} actionName Action to call. + * @param {JSON} data JSON data to send. + * @param {Array} [transfers] Optional list of transfers/ArrayBuffers. + * @returns {Promise} Promise to be resolved with response data. + */ + sendWithPromise: + function messageHandlerSendWithPromise(actionName, data, transfers) { + var callbackId = this.callbackIndex++; + var message = { + action: actionName, + data: data, + callbackId: callbackId + }; + var capability = createPromiseCapability(); + this.callbacksCapabilities[callbackId] = capability; + try { + this.postMessage(message, transfers); + } catch (e) { + capability.reject(e); } + return capability.promise; + }, + /** + * Sends raw message to the comObj. + * @private + * @param message {Object} Raw message. + * @param transfers List of transfers/ArrayBuffers, or undefined. + */ + postMessage: function (message, transfers) { if (transfers && this.postMessageTransfers) { this.comObj.postMessage(message, transfers); } else { @@ -1346,1635 +1558,7 @@ function loadJpegStream(id, imageUrl, objs) { } -var ColorSpace = (function ColorSpaceClosure() { - // Constructor should define this.numComps, this.defaultColor, this.name - function ColorSpace() { - error('should not call ColorSpace constructor'); - } - - ColorSpace.prototype = { - /** - * Converts the color value to the RGB color. The color components are - * located in the src array starting from the srcOffset. Returns the array - * of the rgb components, each value ranging from [0,255]. - */ - getRgb: function ColorSpace_getRgb(src, srcOffset) { - var rgb = new Uint8Array(3); - this.getRgbItem(src, srcOffset, rgb, 0); - return rgb; - }, - /** - * Converts the color value to the RGB color, similar to the getRgb method. - * The result placed into the dest array starting from the destOffset. - */ - getRgbItem: function ColorSpace_getRgbItem(src, srcOffset, - dest, destOffset) { - error('Should not call ColorSpace.getRgbItem'); - }, - /** - * Converts the specified number of the color values to the RGB colors. - * The colors are located in the src array starting from the srcOffset. - * The result is placed into the dest array starting from the destOffset. - * The src array items shall be in [0,2^bits) range, the dest array items - * will be in [0,255] range. alpha01 indicates how many alpha components - * there are in the dest array; it will be either 0 (RGB array) or 1 (RGBA - * array). - */ - getRgbBuffer: function ColorSpace_getRgbBuffer(src, srcOffset, count, - dest, destOffset, bits, - alpha01) { - error('Should not call ColorSpace.getRgbBuffer'); - }, - /** - * Determines the number of bytes required to store the result of the - * conversion done by the getRgbBuffer method. As in getRgbBuffer, - * |alpha01| is either 0 (RGB output) or 1 (RGBA output). - */ - getOutputLength: function ColorSpace_getOutputLength(inputLength, - alpha01) { - error('Should not call ColorSpace.getOutputLength'); - }, - /** - * Returns true if source data will be equal the result/output data. - */ - isPassthrough: function ColorSpace_isPassthrough(bits) { - return false; - }, - /** - * Fills in the RGB colors in the destination buffer. alpha01 indicates - * how many alpha components there are in the dest array; it will be either - * 0 (RGB array) or 1 (RGBA array). - */ - fillRgb: function ColorSpace_fillRgb(dest, originalWidth, - originalHeight, width, height, - actualHeight, bpc, comps, alpha01) { - var count = originalWidth * originalHeight; - var rgbBuf = null; - var numComponentColors = 1 << bpc; - var needsResizing = originalHeight != height || originalWidth != width; - - if (this.isPassthrough(bpc)) { - rgbBuf = comps; - - } else if (this.numComps === 1 && count > numComponentColors && - this.name !== 'DeviceGray' && this.name !== 'DeviceRGB') { - // Optimization: create a color map when there is just one component and - // we are converting more colors than the size of the color map. We - // don't build the map if the colorspace is gray or rgb since those - // methods are faster than building a map. This mainly offers big speed - // ups for indexed and alternate colorspaces. - // - // TODO it may be worth while to cache the color map. While running - // testing I never hit a cache so I will leave that out for now (perhaps - // we are reparsing colorspaces too much?). - var allColors = bpc <= 8 ? new Uint8Array(numComponentColors) : - new Uint16Array(numComponentColors); - for (var i = 0; i < numComponentColors; i++) { - allColors[i] = i; - } - var colorMap = new Uint8Array(numComponentColors * 3); - this.getRgbBuffer(allColors, 0, numComponentColors, colorMap, 0, bpc, - /* alpha01 = */ 0); - - if (!needsResizing) { - // Fill in the RGB values directly into |dest|. - var destPos = 0; - for (var i = 0; i < count; ++i) { - var key = comps[i] * 3; - dest[destPos++] = colorMap[key]; - dest[destPos++] = colorMap[key + 1]; - dest[destPos++] = colorMap[key + 2]; - destPos += alpha01; - } - } else { - rgbBuf = new Uint8Array(count * 3); - var rgbPos = 0; - for (var i = 0; i < count; ++i) { - var key = comps[i] * 3; - rgbBuf[rgbPos++] = colorMap[key]; - rgbBuf[rgbPos++] = colorMap[key + 1]; - rgbBuf[rgbPos++] = colorMap[key + 2]; - } - } - } else { - if (!needsResizing) { - // Fill in the RGB values directly into |dest|. - this.getRgbBuffer(comps, 0, width * actualHeight, dest, 0, bpc, - alpha01); - } else { - rgbBuf = new Uint8Array(count * 3); - this.getRgbBuffer(comps, 0, count, rgbBuf, 0, bpc, - /* alpha01 = */ 0); - } - } - - if (rgbBuf) { - if (needsResizing) { - rgbBuf = PDFImage.resize(rgbBuf, bpc, 3, originalWidth, - originalHeight, width, height); - } - var rgbPos = 0; - var destPos = 0; - for (var i = 0, ii = width * actualHeight; i < ii; i++) { - dest[destPos++] = rgbBuf[rgbPos++]; - dest[destPos++] = rgbBuf[rgbPos++]; - dest[destPos++] = rgbBuf[rgbPos++]; - destPos += alpha01; - } - } - }, - /** - * True if the colorspace has components in the default range of [0, 1]. - * This should be true for all colorspaces except for lab color spaces - * which are [0,100], [-128, 127], [-128, 127]. - */ - usesZeroToOneRange: true - }; - - ColorSpace.parse = function ColorSpace_parse(cs, xref, res) { - var IR = ColorSpace.parseToIR(cs, xref, res); - if (IR instanceof AlternateCS) { - return IR; - } - return ColorSpace.fromIR(IR); - }; - - ColorSpace.fromIR = function ColorSpace_fromIR(IR) { - var name = isArray(IR) ? IR[0] : IR; - - switch (name) { - case 'DeviceGrayCS': - return this.singletons.gray; - case 'DeviceRgbCS': - return this.singletons.rgb; - case 'DeviceCmykCS': - return this.singletons.cmyk; - case 'CalGrayCS': - var whitePoint = IR[1].WhitePoint; - var blackPoint = IR[1].BlackPoint; - var gamma = IR[1].Gamma; - return new CalGrayCS(whitePoint, blackPoint, gamma); - case 'PatternCS': - var basePatternCS = IR[1]; - if (basePatternCS) { - basePatternCS = ColorSpace.fromIR(basePatternCS); - } - return new PatternCS(basePatternCS); - case 'IndexedCS': - var baseIndexedCS = IR[1]; - var hiVal = IR[2]; - var lookup = IR[3]; - return new IndexedCS(ColorSpace.fromIR(baseIndexedCS), hiVal, lookup); - case 'AlternateCS': - var numComps = IR[1]; - var alt = IR[2]; - var tintFnIR = IR[3]; - - return new AlternateCS(numComps, ColorSpace.fromIR(alt), - PDFFunction.fromIR(tintFnIR)); - case 'LabCS': - var whitePoint = IR[1].WhitePoint; - var blackPoint = IR[1].BlackPoint; - var range = IR[1].Range; - return new LabCS(whitePoint, blackPoint, range); - default: - error('Unkown name ' + name); - } - return null; - }; - - ColorSpace.parseToIR = function ColorSpace_parseToIR(cs, xref, res) { - if (isName(cs)) { - var colorSpaces = res.get('ColorSpace'); - if (isDict(colorSpaces)) { - var refcs = colorSpaces.get(cs.name); - if (refcs) { - cs = refcs; - } - } - } - - cs = xref.fetchIfRef(cs); - var mode; - - if (isName(cs)) { - mode = cs.name; - this.mode = mode; - - switch (mode) { - case 'DeviceGray': - case 'G': - return 'DeviceGrayCS'; - case 'DeviceRGB': - case 'RGB': - return 'DeviceRgbCS'; - case 'DeviceCMYK': - case 'CMYK': - return 'DeviceCmykCS'; - case 'Pattern': - return ['PatternCS', null]; - default: - error('unrecognized colorspace ' + mode); - } - } else if (isArray(cs)) { - mode = cs[0].name; - this.mode = mode; - - switch (mode) { - case 'DeviceGray': - case 'G': - return 'DeviceGrayCS'; - case 'DeviceRGB': - case 'RGB': - return 'DeviceRgbCS'; - case 'DeviceCMYK': - case 'CMYK': - return 'DeviceCmykCS'; - case 'CalGray': - var params = cs[1].getAll(); - return ['CalGrayCS', params]; - case 'CalRGB': - return 'DeviceRgbCS'; - case 'ICCBased': - var stream = xref.fetchIfRef(cs[1]); - var dict = stream.dict; - var numComps = dict.get('N'); - if (numComps == 1) { - return 'DeviceGrayCS'; - } else if (numComps == 3) { - return 'DeviceRgbCS'; - } else if (numComps == 4) { - return 'DeviceCmykCS'; - } - break; - case 'Pattern': - var basePatternCS = cs[1]; - if (basePatternCS) { - basePatternCS = ColorSpace.parseToIR(basePatternCS, xref, res); - } - return ['PatternCS', basePatternCS]; - case 'Indexed': - case 'I': - var baseIndexedCS = ColorSpace.parseToIR(cs[1], xref, res); - var hiVal = cs[2] + 1; - var lookup = xref.fetchIfRef(cs[3]); - if (isStream(lookup)) { - lookup = lookup.getBytes(); - } - return ['IndexedCS', baseIndexedCS, hiVal, lookup]; - case 'Separation': - case 'DeviceN': - var name = cs[1]; - var numComps = 1; - if (isName(name)) { - numComps = 1; - } else if (isArray(name)) { - numComps = name.length; - } - var alt = ColorSpace.parseToIR(cs[2], xref, res); - var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3])); - return ['AlternateCS', numComps, alt, tintFnIR]; - case 'Lab': - var params = cs[1].getAll(); - return ['LabCS', params]; - default: - error('unimplemented color space object "' + mode + '"'); - } - } else { - error('unrecognized color space object: "' + cs + '"'); - } - return null; - }; - /** - * Checks if a decode map matches the default decode map for a color space. - * This handles the general decode maps where there are two values per - * component. e.g. [0, 1, 0, 1, 0, 1] for a RGB color. - * This does not handle Lab, Indexed, or Pattern decode maps since they are - * slightly different. - * @param {Array} decode Decode map (usually from an image). - * @param {Number} n Number of components the color space has. - */ - ColorSpace.isDefaultDecode = function ColorSpace_isDefaultDecode(decode, n) { - if (!decode) { - return true; - } - - if (n * 2 !== decode.length) { - warn('The decode map is not the correct length'); - return true; - } - for (var i = 0, ii = decode.length; i < ii; i += 2) { - if (decode[i] !== 0 || decode[i + 1] != 1) { - return false; - } - } - return true; - }; - - ColorSpace.singletons = { - get gray() { - return shadow(this, 'gray', new DeviceGrayCS()); - }, - get rgb() { - return shadow(this, 'rgb', new DeviceRgbCS()); - }, - get cmyk() { - return shadow(this, 'cmyk', new DeviceCmykCS()); - } - }; - - return ColorSpace; -})(); - -/** - * Alternate color space handles both Separation and DeviceN color spaces. A - * Separation color space is actually just a DeviceN with one color component. - * Both color spaces use a tinting function to convert colors to a base color - * space. - */ -var AlternateCS = (function AlternateCSClosure() { - function AlternateCS(numComps, base, tintFn) { - this.name = 'Alternate'; - this.numComps = numComps; - this.defaultColor = new Float32Array(numComps); - for (var i = 0; i < numComps; ++i) { - this.defaultColor[i] = 1; - } - this.base = base; - this.tintFn = tintFn; - } - - AlternateCS.prototype = { - getRgb: ColorSpace.prototype.getRgb, - getRgbItem: function AlternateCS_getRgbItem(src, srcOffset, - dest, destOffset) { - var baseNumComps = this.base.numComps; - var input = 'subarray' in src ? - src.subarray(srcOffset, srcOffset + this.numComps) : - Array.prototype.slice.call(src, srcOffset, srcOffset + this.numComps); - var tinted = this.tintFn(input); - this.base.getRgbItem(tinted, 0, dest, destOffset); - }, - getRgbBuffer: function AlternateCS_getRgbBuffer(src, srcOffset, count, - dest, destOffset, bits, - alpha01) { - var tintFn = this.tintFn; - var base = this.base; - var scale = 1 / ((1 << bits) - 1); - var baseNumComps = base.numComps; - var usesZeroToOneRange = base.usesZeroToOneRange; - var isPassthrough = (base.isPassthrough(8) || !usesZeroToOneRange) && - alpha01 === 0; - var pos = isPassthrough ? destOffset : 0; - var baseBuf = isPassthrough ? dest : new Uint8Array(baseNumComps * count); - var numComps = this.numComps; - - var scaled = new Float32Array(numComps); - for (var i = 0; i < count; i++) { - for (var j = 0; j < numComps; j++) { - scaled[j] = src[srcOffset++] * scale; - } - var tinted = tintFn(scaled); - if (usesZeroToOneRange) { - for (var j = 0; j < baseNumComps; j++) { - baseBuf[pos++] = tinted[j] * 255; - } - } else { - base.getRgbItem(tinted, 0, baseBuf, pos); - pos += baseNumComps; - } - } - if (!isPassthrough) { - base.getRgbBuffer(baseBuf, 0, count, dest, destOffset, 8, alpha01); - } - }, - getOutputLength: function AlternateCS_getOutputLength(inputLength, - alpha01) { - return this.base.getOutputLength(inputLength * - this.base.numComps / this.numComps, - alpha01); - }, - isPassthrough: ColorSpace.prototype.isPassthrough, - fillRgb: ColorSpace.prototype.fillRgb, - isDefaultDecode: function AlternateCS_isDefaultDecode(decodeMap) { - return ColorSpace.isDefaultDecode(decodeMap, this.numComps); - }, - usesZeroToOneRange: true - }; - - return AlternateCS; -})(); - -var PatternCS = (function PatternCSClosure() { - function PatternCS(baseCS) { - this.name = 'Pattern'; - this.base = baseCS; - } - PatternCS.prototype = {}; - - return PatternCS; -})(); - -var IndexedCS = (function IndexedCSClosure() { - function IndexedCS(base, highVal, lookup) { - this.name = 'Indexed'; - this.numComps = 1; - this.defaultColor = new Uint8Array([0]); - this.base = base; - this.highVal = highVal; - - var baseNumComps = base.numComps; - var length = baseNumComps * highVal; - var lookupArray; - - if (isStream(lookup)) { - lookupArray = new Uint8Array(length); - var bytes = lookup.getBytes(length); - lookupArray.set(bytes); - } else if (isString(lookup)) { - lookupArray = new Uint8Array(length); - for (var i = 0; i < length; ++i) { - lookupArray[i] = lookup.charCodeAt(i); - } - } else if (lookup instanceof Uint8Array || lookup instanceof Array) { - lookupArray = lookup; - } else { - error('Unrecognized lookup table: ' + lookup); - } - this.lookup = lookupArray; - } - - IndexedCS.prototype = { - getRgb: ColorSpace.prototype.getRgb, - getRgbItem: function IndexedCS_getRgbItem(src, srcOffset, - dest, destOffset) { - var numComps = this.base.numComps; - var start = src[srcOffset] * numComps; - this.base.getRgbItem(this.lookup, start, dest, destOffset); - }, - getRgbBuffer: function IndexedCS_getRgbBuffer(src, srcOffset, count, - dest, destOffset, bits, - alpha01) { - var base = this.base; - var numComps = base.numComps; - var outputDelta = base.getOutputLength(numComps, alpha01); - var lookup = this.lookup; - - for (var i = 0; i < count; ++i) { - var lookupPos = src[srcOffset++] * numComps; - base.getRgbBuffer(lookup, lookupPos, 1, dest, destOffset, 8, alpha01); - destOffset += outputDelta; - } - }, - getOutputLength: function IndexedCS_getOutputLength(inputLength, alpha01) { - return this.base.getOutputLength(inputLength * this.base.numComps, - alpha01); - }, - isPassthrough: ColorSpace.prototype.isPassthrough, - fillRgb: ColorSpace.prototype.fillRgb, - isDefaultDecode: function IndexedCS_isDefaultDecode(decodeMap) { - // indexed color maps shouldn't be changed - return true; - }, - usesZeroToOneRange: true - }; - return IndexedCS; -})(); - -var DeviceGrayCS = (function DeviceGrayCSClosure() { - function DeviceGrayCS() { - this.name = 'DeviceGray'; - this.numComps = 1; - this.defaultColor = new Float32Array([0]); - } - - DeviceGrayCS.prototype = { - getRgb: ColorSpace.prototype.getRgb, - getRgbItem: function DeviceGrayCS_getRgbItem(src, srcOffset, - dest, destOffset) { - var c = (src[srcOffset] * 255) | 0; - c = c < 0 ? 0 : c > 255 ? 255 : c; - dest[destOffset] = dest[destOffset + 1] = dest[destOffset + 2] = c; - }, - getRgbBuffer: function DeviceGrayCS_getRgbBuffer(src, srcOffset, count, - dest, destOffset, bits, - alpha01) { - var scale = 255 / ((1 << bits) - 1); - var j = srcOffset, q = destOffset; - for (var i = 0; i < count; ++i) { - var c = (scale * src[j++]) | 0; - dest[q++] = c; - dest[q++] = c; - dest[q++] = c; - q += alpha01; - } - }, - getOutputLength: function DeviceGrayCS_getOutputLength(inputLength, - alpha01) { - return inputLength * (3 + alpha01); - }, - isPassthrough: ColorSpace.prototype.isPassthrough, - fillRgb: ColorSpace.prototype.fillRgb, - isDefaultDecode: function DeviceGrayCS_isDefaultDecode(decodeMap) { - return ColorSpace.isDefaultDecode(decodeMap, this.numComps); - }, - usesZeroToOneRange: true - }; - return DeviceGrayCS; -})(); - -var DeviceRgbCS = (function DeviceRgbCSClosure() { - function DeviceRgbCS() { - this.name = 'DeviceRGB'; - this.numComps = 3; - this.defaultColor = new Float32Array([0, 0, 0]); - } - DeviceRgbCS.prototype = { - getRgb: ColorSpace.prototype.getRgb, - getRgbItem: function DeviceRgbCS_getRgbItem(src, srcOffset, - dest, destOffset) { - var r = (src[srcOffset] * 255) | 0; - var g = (src[srcOffset + 1] * 255) | 0; - var b = (src[srcOffset + 2] * 255) | 0; - dest[destOffset] = r < 0 ? 0 : r > 255 ? 255 : r; - dest[destOffset + 1] = g < 0 ? 0 : g > 255 ? 255 : g; - dest[destOffset + 2] = b < 0 ? 0 : b > 255 ? 255 : b; - }, - getRgbBuffer: function DeviceRgbCS_getRgbBuffer(src, srcOffset, count, - dest, destOffset, bits, - alpha01) { - if (bits === 8 && alpha01 === 0) { - dest.set(src.subarray(srcOffset, srcOffset + count * 3), destOffset); - return; - } - var scale = 255 / ((1 << bits) - 1); - var j = srcOffset, q = destOffset; - for (var i = 0; i < count; ++i) { - dest[q++] = (scale * src[j++]) | 0; - dest[q++] = (scale * src[j++]) | 0; - dest[q++] = (scale * src[j++]) | 0; - q += alpha01; - } - }, - getOutputLength: function DeviceRgbCS_getOutputLength(inputLength, - alpha01) { - return (inputLength * (3 + alpha01) / 3) | 0; - }, - isPassthrough: function DeviceRgbCS_isPassthrough(bits) { - return bits == 8; - }, - fillRgb: ColorSpace.prototype.fillRgb, - isDefaultDecode: function DeviceRgbCS_isDefaultDecode(decodeMap) { - return ColorSpace.isDefaultDecode(decodeMap, this.numComps); - }, - usesZeroToOneRange: true - }; - return DeviceRgbCS; -})(); - -var DeviceCmykCS = (function DeviceCmykCSClosure() { - // The coefficients below was found using numerical analysis: the method of - // steepest descent for the sum((f_i - color_value_i)^2) for r/g/b colors, - // where color_value is the tabular value from the table of sampled RGB colors - // from CMYK US Web Coated (SWOP) colorspace, and f_i is the corresponding - // CMYK color conversion using the estimation below: - // f(A, B,.. N) = Acc+Bcm+Ccy+Dck+c+Fmm+Gmy+Hmk+Im+Jyy+Kyk+Ly+Mkk+Nk+255 - function convertToRgb(src, srcOffset, srcScale, dest, destOffset) { - var c = src[srcOffset + 0] * srcScale; - var m = src[srcOffset + 1] * srcScale; - var y = src[srcOffset + 2] * srcScale; - var k = src[srcOffset + 3] * srcScale; - - var r = - c * (-4.387332384609988 * c + 54.48615194189176 * m + - 18.82290502165302 * y + 212.25662451639585 * k + - -285.2331026137004) + - m * (1.7149763477362134 * m - 5.6096736904047315 * y + - -17.873870861415444 * k - 5.497006427196366) + - y * (-2.5217340131683033 * y - 21.248923337353073 * k + - 17.5119270841813) + - k * (-21.86122147463605 * k - 189.48180835922747) + 255; - var g = - c * (8.841041422036149 * c + 60.118027045597366 * m + - 6.871425592049007 * y + 31.159100130055922 * k + - -79.2970844816548) + - m * (-15.310361306967817 * m + 17.575251261109482 * y + - 131.35250912493976 * k - 190.9453302588951) + - y * (4.444339102852739 * y + 9.8632861493405 * k - 24.86741582555878) + - k * (-20.737325471181034 * k - 187.80453709719578) + 255; - var b = - c * (0.8842522430003296 * c + 8.078677503112928 * m + - 30.89978309703729 * y - 0.23883238689178934 * k + - -14.183576799673286) + - m * (10.49593273432072 * m + 63.02378494754052 * y + - 50.606957656360734 * k - 112.23884253719248) + - y * (0.03296041114873217 * y + 115.60384449646641 * k + - -193.58209356861505) + - k * (-22.33816807309886 * k - 180.12613974708367) + 255; - - dest[destOffset] = r > 255 ? 255 : r < 0 ? 0 : r; - dest[destOffset + 1] = g > 255 ? 255 : g < 0 ? 0 : g; - dest[destOffset + 2] = b > 255 ? 255 : b < 0 ? 0 : b; - } - - function DeviceCmykCS() { - this.name = 'DeviceCMYK'; - this.numComps = 4; - this.defaultColor = new Float32Array([0, 0, 0, 1]); - } - DeviceCmykCS.prototype = { - getRgb: ColorSpace.prototype.getRgb, - getRgbItem: function DeviceCmykCS_getRgbItem(src, srcOffset, - dest, destOffset) { - convertToRgb(src, srcOffset, 1, dest, destOffset); - }, - getRgbBuffer: function DeviceCmykCS_getRgbBuffer(src, srcOffset, count, - dest, destOffset, bits, - alpha01) { - var scale = 1 / ((1 << bits) - 1); - for (var i = 0; i < count; i++) { - convertToRgb(src, srcOffset, scale, dest, destOffset); - srcOffset += 4; - destOffset += 3 + alpha01; - } - }, - getOutputLength: function DeviceCmykCS_getOutputLength(inputLength, - alpha01) { - return (inputLength / 4 * (3 + alpha01)) | 0; - }, - isPassthrough: ColorSpace.prototype.isPassthrough, - fillRgb: ColorSpace.prototype.fillRgb, - isDefaultDecode: function DeviceCmykCS_isDefaultDecode(decodeMap) { - return ColorSpace.isDefaultDecode(decodeMap, this.numComps); - }, - usesZeroToOneRange: true - }; - - return DeviceCmykCS; -})(); - -// -// CalGrayCS: Based on "PDF Reference, Sixth Ed", p.245 -// -var CalGrayCS = (function CalGrayCSClosure() { - function CalGrayCS(whitePoint, blackPoint, gamma) { - this.name = 'CalGray'; - this.numComps = 1; - this.defaultColor = new Float32Array([0]); - - if (!whitePoint) { - error('WhitePoint missing - required for color space CalGray'); - } - blackPoint = blackPoint || [0, 0, 0]; - gamma = gamma || 1; - - // Translate arguments to spec variables. - this.XW = whitePoint[0]; - this.YW = whitePoint[1]; - this.ZW = whitePoint[2]; - - this.XB = blackPoint[0]; - this.YB = blackPoint[1]; - this.ZB = blackPoint[2]; - - this.G = gamma; - - // Validate variables as per spec. - if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) { - error('Invalid WhitePoint components for ' + this.name + - ', no fallback available'); - } - - if (this.XB < 0 || this.YB < 0 || this.ZB < 0) { - info('Invalid BlackPoint for ' + this.name + ', falling back to default'); - this.XB = this.YB = this.ZB = 0; - } - - if (this.XB !== 0 || this.YB !== 0 || this.ZB !== 0) { - warn(this.name + ', BlackPoint: XB: ' + this.XB + ', YB: ' + this.YB + - ', ZB: ' + this.ZB + ', only default values are supported.'); - } - - if (this.G < 1) { - info('Invalid Gamma: ' + this.G + ' for ' + this.name + - ', falling back to default'); - this.G = 1; - } - } - - function convertToRgb(cs, src, srcOffset, dest, destOffset, scale) { - // A represents a gray component of a calibrated gray space. - // A <---> AG in the spec - var A = src[srcOffset] * scale; - var AG = Math.pow(A, cs.G); - - // Computes intermediate variables M, L, N as per spec. - // Except if other than default BlackPoint values are used. - var M = cs.XW * AG; - var L = cs.YW * AG; - var N = cs.ZW * AG; - - // Decode XYZ, as per spec. - var X = M; - var Y = L; - var Z = N; - - // http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html, Ch 4. - // This yields values in range [0, 100]. - var Lstar = Math.max(116 * Math.pow(Y, 1 / 3) - 16, 0); - - // Convert values to rgb range [0, 255]. - dest[destOffset] = Lstar * 255 / 100; - dest[destOffset + 1] = Lstar * 255 / 100; - dest[destOffset + 2] = Lstar * 255 / 100; - } - - CalGrayCS.prototype = { - getRgb: ColorSpace.prototype.getRgb, - getRgbItem: function CalGrayCS_getRgbItem(src, srcOffset, - dest, destOffset) { - convertToRgb(this, src, srcOffset, dest, destOffset, 1); - }, - getRgbBuffer: function CalGrayCS_getRgbBuffer(src, srcOffset, count, - dest, destOffset, bits, - alpha01) { - var scale = 1 / ((1 << bits) - 1); - - for (var i = 0; i < count; ++i) { - convertToRgb(this, src, srcOffset, dest, destOffset, scale); - srcOffset += 1; - destOffset += 3 + alpha01; - } - }, - getOutputLength: function CalGrayCS_getOutputLength(inputLength, alpha01) { - return inputLength * (3 + alpha01); - }, - isPassthrough: ColorSpace.prototype.isPassthrough, - fillRgb: ColorSpace.prototype.fillRgb, - isDefaultDecode: function CalGrayCS_isDefaultDecode(decodeMap) { - return ColorSpace.isDefaultDecode(decodeMap, this.numComps); - }, - usesZeroToOneRange: true - }; - return CalGrayCS; -})(); - -// -// LabCS: Based on "PDF Reference, Sixth Ed", p.250 -// -var LabCS = (function LabCSClosure() { - function LabCS(whitePoint, blackPoint, range) { - this.name = 'Lab'; - this.numComps = 3; - this.defaultColor = new Float32Array([0, 0, 0]); - - if (!whitePoint) { - error('WhitePoint missing - required for color space Lab'); - } - blackPoint = blackPoint || [0, 0, 0]; - range = range || [-100, 100, -100, 100]; - - // Translate args to spec variables - this.XW = whitePoint[0]; - this.YW = whitePoint[1]; - this.ZW = whitePoint[2]; - this.amin = range[0]; - this.amax = range[1]; - this.bmin = range[2]; - this.bmax = range[3]; - - // These are here just for completeness - the spec doesn't offer any - // formulas that use BlackPoint in Lab - this.XB = blackPoint[0]; - this.YB = blackPoint[1]; - this.ZB = blackPoint[2]; - - // Validate vars as per spec - if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) { - error('Invalid WhitePoint components, no fallback available'); - } - - if (this.XB < 0 || this.YB < 0 || this.ZB < 0) { - info('Invalid BlackPoint, falling back to default'); - this.XB = this.YB = this.ZB = 0; - } - - if (this.amin > this.amax || this.bmin > this.bmax) { - info('Invalid Range, falling back to defaults'); - this.amin = -100; - this.amax = 100; - this.bmin = -100; - this.bmax = 100; - } - } - - // Function g(x) from spec - function fn_g(x) { - if (x >= 6 / 29) { - return x * x * x; - } else { - return (108 / 841) * (x - 4 / 29); - } - } - - function decode(value, high1, low2, high2) { - return low2 + (value) * (high2 - low2) / (high1); - } - - // If decoding is needed maxVal should be 2^bits per component - 1. - function convertToRgb(cs, src, srcOffset, maxVal, dest, destOffset) { - // XXX: Lab input is in the range of [0, 100], [amin, amax], [bmin, bmax] - // not the usual [0, 1]. If a command like setFillColor is used the src - // values will already be within the correct range. However, if we are - // converting an image we have to map the values to the correct range given - // above. - // Ls,as,bs <---> L*,a*,b* in the spec - var Ls = src[srcOffset]; - var as = src[srcOffset + 1]; - var bs = src[srcOffset + 2]; - if (maxVal !== false) { - Ls = decode(Ls, maxVal, 0, 100); - as = decode(as, maxVal, cs.amin, cs.amax); - bs = decode(bs, maxVal, cs.bmin, cs.bmax); - } - - // Adjust limits of 'as' and 'bs' - as = as > cs.amax ? cs.amax : as < cs.amin ? cs.amin : as; - bs = bs > cs.bmax ? cs.bmax : bs < cs.bmin ? cs.bmin : bs; - - // Computes intermediate variables X,Y,Z as per spec - var M = (Ls + 16) / 116; - var L = M + (as / 500); - var N = M - (bs / 200); - - var X = cs.XW * fn_g(L); - var Y = cs.YW * fn_g(M); - var Z = cs.ZW * fn_g(N); - - var r, g, b; - // Using different conversions for D50 and D65 white points, - // per http://www.color.org/srgb.pdf - if (cs.ZW < 1) { - // Assuming D50 (X=0.9642, Y=1.00, Z=0.8249) - r = X * 3.1339 + Y * -1.6170 + Z * -0.4906; - g = X * -0.9785 + Y * 1.9160 + Z * 0.0333; - b = X * 0.0720 + Y * -0.2290 + Z * 1.4057; - } else { - // Assuming D65 (X=0.9505, Y=1.00, Z=1.0888) - r = X * 3.2406 + Y * -1.5372 + Z * -0.4986; - g = X * -0.9689 + Y * 1.8758 + Z * 0.0415; - b = X * 0.0557 + Y * -0.2040 + Z * 1.0570; - } - // clamp color values to [0,1] range then convert to [0,255] range. - dest[destOffset] = r <= 0 ? 0 : r >= 1 ? 255 : Math.sqrt(r) * 255 | 0; - dest[destOffset + 1] = g <= 0 ? 0 : g >= 1 ? 255 : Math.sqrt(g) * 255 | 0; - dest[destOffset + 2] = b <= 0 ? 0 : b >= 1 ? 255 : Math.sqrt(b) * 255 | 0; - } - - LabCS.prototype = { - getRgb: ColorSpace.prototype.getRgb, - getRgbItem: function LabCS_getRgbItem(src, srcOffset, dest, destOffset) { - convertToRgb(this, src, srcOffset, false, dest, destOffset); - }, - getRgbBuffer: function LabCS_getRgbBuffer(src, srcOffset, count, - dest, destOffset, bits, - alpha01) { - var maxVal = (1 << bits) - 1; - for (var i = 0; i < count; i++) { - convertToRgb(this, src, srcOffset, maxVal, dest, destOffset); - srcOffset += 3; - destOffset += 3 + alpha01; - } - }, - getOutputLength: function LabCS_getOutputLength(inputLength, alpha01) { - return (inputLength * (3 + alpha01) / 3) | 0; - }, - isPassthrough: ColorSpace.prototype.isPassthrough, - isDefaultDecode: function LabCS_isDefaultDecode(decodeMap) { - // XXX: Decoding is handled with the lab conversion because of the strange - // ranges that are used. - return true; - }, - usesZeroToOneRange: false - }; - return LabCS; -})(); - - - -var PDFFunction = (function PDFFunctionClosure() { - var CONSTRUCT_SAMPLED = 0; - var CONSTRUCT_INTERPOLATED = 2; - var CONSTRUCT_STICHED = 3; - var CONSTRUCT_POSTSCRIPT = 4; - - return { - getSampleArray: function PDFFunction_getSampleArray(size, outputSize, bps, - str) { - var length = 1; - for (var i = 0, ii = size.length; i < ii; i++) { - length *= size[i]; - } - length *= outputSize; - - var array = []; - var codeSize = 0; - var codeBuf = 0; - // 32 is a valid bps so shifting won't work - var sampleMul = 1.0 / (Math.pow(2.0, bps) - 1); - - var strBytes = str.getBytes((length * bps + 7) / 8); - var strIdx = 0; - for (var i = 0; i < length; i++) { - while (codeSize < bps) { - codeBuf <<= 8; - codeBuf |= strBytes[strIdx++]; - codeSize += 8; - } - codeSize -= bps; - array.push((codeBuf >> codeSize) * sampleMul); - codeBuf &= (1 << codeSize) - 1; - } - return array; - }, - - getIR: function PDFFunction_getIR(xref, fn) { - var dict = fn.dict; - if (!dict) { - dict = fn; - } - - var types = [this.constructSampled, - null, - this.constructInterpolated, - this.constructStiched, - this.constructPostScript]; - - var typeNum = dict.get('FunctionType'); - var typeFn = types[typeNum]; - if (!typeFn) { - error('Unknown type of function'); - } - - return typeFn.call(this, fn, dict, xref); - }, - - fromIR: function PDFFunction_fromIR(IR) { - var type = IR[0]; - switch (type) { - case CONSTRUCT_SAMPLED: - return this.constructSampledFromIR(IR); - case CONSTRUCT_INTERPOLATED: - return this.constructInterpolatedFromIR(IR); - case CONSTRUCT_STICHED: - return this.constructStichedFromIR(IR); - //case CONSTRUCT_POSTSCRIPT: - default: - return this.constructPostScriptFromIR(IR); - } - }, - - parse: function PDFFunction_parse(xref, fn) { - var IR = this.getIR(xref, fn); - return this.fromIR(IR); - }, - - constructSampled: function PDFFunction_constructSampled(str, dict) { - function toMultiArray(arr) { - var inputLength = arr.length; - var outputLength = arr.length / 2; - var out = []; - var index = 0; - for (var i = 0; i < inputLength; i += 2) { - out[index] = [arr[i], arr[i + 1]]; - ++index; - } - return out; - } - var domain = dict.get('Domain'); - var range = dict.get('Range'); - - if (!domain || !range) { - error('No domain or range'); - } - - var inputSize = domain.length / 2; - var outputSize = range.length / 2; - - domain = toMultiArray(domain); - range = toMultiArray(range); - - var size = dict.get('Size'); - var bps = dict.get('BitsPerSample'); - var order = dict.get('Order') || 1; - if (order !== 1) { - // No description how cubic spline interpolation works in PDF32000:2008 - // As in poppler, ignoring order, linear interpolation may work as good - info('No support for cubic spline interpolation: ' + order); - } - - var encode = dict.get('Encode'); - if (!encode) { - encode = []; - for (var i = 0; i < inputSize; ++i) { - encode.push(0); - encode.push(size[i] - 1); - } - } - encode = toMultiArray(encode); - - var decode = dict.get('Decode'); - if (!decode) { - decode = range; - } else { - decode = toMultiArray(decode); - } - - var samples = this.getSampleArray(size, outputSize, bps, str); - - return [ - CONSTRUCT_SAMPLED, inputSize, domain, encode, decode, samples, size, - outputSize, Math.pow(2, bps) - 1, range - ]; - }, - - constructSampledFromIR: function PDFFunction_constructSampledFromIR(IR) { - // See chapter 3, page 109 of the PDF reference - function interpolate(x, xmin, xmax, ymin, ymax) { - return ymin + ((x - xmin) * ((ymax - ymin) / (xmax - xmin))); - } - - return function constructSampledFromIRResult(args) { - // See chapter 3, page 110 of the PDF reference. - var m = IR[1]; - var domain = IR[2]; - var encode = IR[3]; - var decode = IR[4]; - var samples = IR[5]; - var size = IR[6]; - var n = IR[7]; - var mask = IR[8]; - var range = IR[9]; - - if (m != args.length) { - error('Incorrect number of arguments: ' + m + ' != ' + - args.length); - } - - var x = args; - - // Building the cube vertices: its part and sample index - // http://rjwagner49.com/Mathematics/Interpolation.pdf - var cubeVertices = 1 << m; - var cubeN = new Float64Array(cubeVertices); - var cubeVertex = new Uint32Array(cubeVertices); - for (var j = 0; j < cubeVertices; j++) { - cubeN[j] = 1; - } - - var k = n, pos = 1; - // Map x_i to y_j for 0 <= i < m using the sampled function. - for (var i = 0; i < m; ++i) { - // x_i' = min(max(x_i, Domain_2i), Domain_2i+1) - var domain_2i = domain[i][0]; - var domain_2i_1 = domain[i][1]; - var xi = Math.min(Math.max(x[i], domain_2i), domain_2i_1); - - // e_i = Interpolate(x_i', Domain_2i, Domain_2i+1, - // Encode_2i, Encode_2i+1) - var e = interpolate(xi, domain_2i, domain_2i_1, - encode[i][0], encode[i][1]); - - // e_i' = min(max(e_i, 0), Size_i - 1) - var size_i = size[i]; - e = Math.min(Math.max(e, 0), size_i - 1); - - // Adjusting the cube: N and vertex sample index - var e0 = e < size_i - 1 ? Math.floor(e) : e - 1; // e1 = e0 + 1; - var n0 = e0 + 1 - e; // (e1 - e) / (e1 - e0); - var n1 = e - e0; // (e - e0) / (e1 - e0); - var offset0 = e0 * k; - var offset1 = offset0 + k; // e1 * k - for (var j = 0; j < cubeVertices; j++) { - if (j & pos) { - cubeN[j] *= n1; - cubeVertex[j] += offset1; - } else { - cubeN[j] *= n0; - cubeVertex[j] += offset0; - } - } - - k *= size_i; - pos <<= 1; - } - - var y = new Float64Array(n); - for (var j = 0; j < n; ++j) { - // Sum all cube vertices' samples portions - var rj = 0; - for (var i = 0; i < cubeVertices; i++) { - rj += samples[cubeVertex[i] + j] * cubeN[i]; - } - - // r_j' = Interpolate(r_j, 0, 2^BitsPerSample - 1, - // Decode_2j, Decode_2j+1) - rj = interpolate(rj, 0, 1, decode[j][0], decode[j][1]); - - // y_j = min(max(r_j, range_2j), range_2j+1) - y[j] = Math.min(Math.max(rj, range[j][0]), range[j][1]); - } - - return y; - }; - }, - - constructInterpolated: function PDFFunction_constructInterpolated(str, - dict) { - var c0 = dict.get('C0') || [0]; - var c1 = dict.get('C1') || [1]; - var n = dict.get('N'); - - if (!isArray(c0) || !isArray(c1)) { - error('Illegal dictionary for interpolated function'); - } - - var length = c0.length; - var diff = []; - for (var i = 0; i < length; ++i) { - diff.push(c1[i] - c0[i]); - } - - return [CONSTRUCT_INTERPOLATED, c0, diff, n]; - }, - - constructInterpolatedFromIR: - function PDFFunction_constructInterpolatedFromIR(IR) { - var c0 = IR[1]; - var diff = IR[2]; - var n = IR[3]; - - var length = diff.length; - - return function constructInterpolatedFromIRResult(args) { - var x = n == 1 ? args[0] : Math.pow(args[0], n); - - var out = []; - for (var j = 0; j < length; ++j) { - out.push(c0[j] + (x * diff[j])); - } - - return out; - - }; - }, - - constructStiched: function PDFFunction_constructStiched(fn, dict, xref) { - var domain = dict.get('Domain'); - - if (!domain) { - error('No domain'); - } - - var inputSize = domain.length / 2; - if (inputSize != 1) { - error('Bad domain for stiched function'); - } - - var fnRefs = dict.get('Functions'); - var fns = []; - for (var i = 0, ii = fnRefs.length; i < ii; ++i) { - fns.push(PDFFunction.getIR(xref, xref.fetchIfRef(fnRefs[i]))); - } - - var bounds = dict.get('Bounds'); - var encode = dict.get('Encode'); - - return [CONSTRUCT_STICHED, domain, bounds, encode, fns]; - }, - - constructStichedFromIR: function PDFFunction_constructStichedFromIR(IR) { - var domain = IR[1]; - var bounds = IR[2]; - var encode = IR[3]; - var fnsIR = IR[4]; - var fns = []; - - for (var i = 0, ii = fnsIR.length; i < ii; i++) { - fns.push(PDFFunction.fromIR(fnsIR[i])); - } - - return function constructStichedFromIRResult(args) { - var clip = function constructStichedFromIRClip(v, min, max) { - if (v > max) { - v = max; - } else if (v < min) { - v = min; - } - return v; - }; - - // clip to domain - var v = clip(args[0], domain[0], domain[1]); - // calulate which bound the value is in - for (var i = 0, ii = bounds.length; i < ii; ++i) { - if (v < bounds[i]) { - break; - } - } - - // encode value into domain of function - var dmin = domain[0]; - if (i > 0) { - dmin = bounds[i - 1]; - } - var dmax = domain[1]; - if (i < bounds.length) { - dmax = bounds[i]; - } - - var rmin = encode[2 * i]; - var rmax = encode[2 * i + 1]; - - var v2 = rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin); - - // call the appropriate function - return fns[i]([v2]); - }; - }, - - constructPostScript: function PDFFunction_constructPostScript(fn, dict, - xref) { - var domain = dict.get('Domain'); - var range = dict.get('Range'); - - if (!domain) { - error('No domain.'); - } - - if (!range) { - error('No range.'); - } - - var lexer = new PostScriptLexer(fn); - var parser = new PostScriptParser(lexer); - var code = parser.parse(); - - return [CONSTRUCT_POSTSCRIPT, domain, range, code]; - }, - - constructPostScriptFromIR: function PDFFunction_constructPostScriptFromIR( - IR) { - var domain = IR[1]; - var range = IR[2]; - var code = IR[3]; - var numOutputs = range.length / 2; - var evaluator = new PostScriptEvaluator(code); - // Cache the values for a big speed up, the cache size is limited though - // since the number of possible values can be huge from a PS function. - var cache = new FunctionCache(); - return function constructPostScriptFromIRResult(args) { - var initialStack = []; - for (var i = 0, ii = (domain.length / 2); i < ii; ++i) { - initialStack.push(args[i]); - } - - var key = initialStack.join('_'); - if (cache.has(key)) { - return cache.get(key); - } - - var stack = evaluator.execute(initialStack); - var transformed = []; - for (i = numOutputs - 1; i >= 0; --i) { - var out = stack.pop(); - var rangeIndex = 2 * i; - if (out < range[rangeIndex]) { - out = range[rangeIndex]; - } else if (out > range[rangeIndex + 1]) { - out = range[rangeIndex + 1]; - } - transformed[i] = out; - } - cache.set(key, transformed); - return transformed; - }; - } - }; -})(); - -var FunctionCache = (function FunctionCacheClosure() { - // Of 10 PDF's with type4 functions the maxium number of distinct values seen - // was 256. This still may need some tweaking in the future though. - var MAX_CACHE_SIZE = 1024; - function FunctionCache() { - this.cache = {}; - this.total = 0; - } - FunctionCache.prototype = { - has: function FunctionCache_has(key) { - return key in this.cache; - }, - get: function FunctionCache_get(key) { - return this.cache[key]; - }, - set: function FunctionCache_set(key, value) { - if (this.total < MAX_CACHE_SIZE) { - this.cache[key] = value; - this.total++; - } - } - }; - return FunctionCache; -})(); - -var PostScriptStack = (function PostScriptStackClosure() { - var MAX_STACK_SIZE = 100; - function PostScriptStack(initialStack) { - this.stack = initialStack || []; - } - - PostScriptStack.prototype = { - push: function PostScriptStack_push(value) { - if (this.stack.length >= MAX_STACK_SIZE) { - error('PostScript function stack overflow.'); - } - this.stack.push(value); - }, - pop: function PostScriptStack_pop() { - if (this.stack.length <= 0) { - error('PostScript function stack underflow.'); - } - return this.stack.pop(); - }, - copy: function PostScriptStack_copy(n) { - if (this.stack.length + n >= MAX_STACK_SIZE) { - error('PostScript function stack overflow.'); - } - var stack = this.stack; - for (var i = stack.length - n, j = n - 1; j >= 0; j--, i++) { - stack.push(stack[i]); - } - }, - index: function PostScriptStack_index(n) { - this.push(this.stack[this.stack.length - n - 1]); - }, - // rotate the last n stack elements p times - roll: function PostScriptStack_roll(n, p) { - var stack = this.stack; - var l = stack.length - n; - var r = stack.length - 1, c = l + (p - Math.floor(p / n) * n), i, j, t; - for (i = l, j = r; i < j; i++, j--) { - t = stack[i]; stack[i] = stack[j]; stack[j] = t; - } - for (i = l, j = c - 1; i < j; i++, j--) { - t = stack[i]; stack[i] = stack[j]; stack[j] = t; - } - for (i = c, j = r; i < j; i++, j--) { - t = stack[i]; stack[i] = stack[j]; stack[j] = t; - } - } - }; - return PostScriptStack; -})(); -var PostScriptEvaluator = (function PostScriptEvaluatorClosure() { - function PostScriptEvaluator(operators) { - this.operators = operators; - } - PostScriptEvaluator.prototype = { - execute: function PostScriptEvaluator_execute(initialStack) { - var stack = new PostScriptStack(initialStack); - var counter = 0; - var operators = this.operators; - var length = operators.length; - var operator, a, b; - while (counter < length) { - operator = operators[counter++]; - if (typeof operator == 'number') { - // Operator is really an operand and should be pushed to the stack. - stack.push(operator); - continue; - } - switch (operator) { - // non standard ps operators - case 'jz': // jump if false - b = stack.pop(); - a = stack.pop(); - if (!a) { - counter = b; - } - break; - case 'j': // jump - a = stack.pop(); - counter = a; - break; - - // all ps operators in alphabetical order (excluding if/ifelse) - case 'abs': - a = stack.pop(); - stack.push(Math.abs(a)); - break; - case 'add': - b = stack.pop(); - a = stack.pop(); - stack.push(a + b); - break; - case 'and': - b = stack.pop(); - a = stack.pop(); - if (isBool(a) && isBool(b)) { - stack.push(a && b); - } else { - stack.push(a & b); - } - break; - case 'atan': - a = stack.pop(); - stack.push(Math.atan(a)); - break; - case 'bitshift': - b = stack.pop(); - a = stack.pop(); - if (a > 0) { - stack.push(a << b); - } else { - stack.push(a >> b); - } - break; - case 'ceiling': - a = stack.pop(); - stack.push(Math.ceil(a)); - break; - case 'copy': - a = stack.pop(); - stack.copy(a); - break; - case 'cos': - a = stack.pop(); - stack.push(Math.cos(a)); - break; - case 'cvi': - a = stack.pop() | 0; - stack.push(a); - break; - case 'cvr': - // noop - break; - case 'div': - b = stack.pop(); - a = stack.pop(); - stack.push(a / b); - break; - case 'dup': - stack.copy(1); - break; - case 'eq': - b = stack.pop(); - a = stack.pop(); - stack.push(a == b); - break; - case 'exch': - stack.roll(2, 1); - break; - case 'exp': - b = stack.pop(); - a = stack.pop(); - stack.push(Math.pow(a, b)); - break; - case 'false': - stack.push(false); - break; - case 'floor': - a = stack.pop(); - stack.push(Math.floor(a)); - break; - case 'ge': - b = stack.pop(); - a = stack.pop(); - stack.push(a >= b); - break; - case 'gt': - b = stack.pop(); - a = stack.pop(); - stack.push(a > b); - break; - case 'idiv': - b = stack.pop(); - a = stack.pop(); - stack.push((a / b) | 0); - break; - case 'index': - a = stack.pop(); - stack.index(a); - break; - case 'le': - b = stack.pop(); - a = stack.pop(); - stack.push(a <= b); - break; - case 'ln': - a = stack.pop(); - stack.push(Math.log(a)); - break; - case 'log': - a = stack.pop(); - stack.push(Math.log(a) / Math.LN10); - break; - case 'lt': - b = stack.pop(); - a = stack.pop(); - stack.push(a < b); - break; - case 'mod': - b = stack.pop(); - a = stack.pop(); - stack.push(a % b); - break; - case 'mul': - b = stack.pop(); - a = stack.pop(); - stack.push(a * b); - break; - case 'ne': - b = stack.pop(); - a = stack.pop(); - stack.push(a != b); - break; - case 'neg': - a = stack.pop(); - stack.push(-b); - break; - case 'not': - a = stack.pop(); - if (isBool(a) && isBool(b)) { - stack.push(a && b); - } else { - stack.push(a & b); - } - break; - case 'or': - b = stack.pop(); - a = stack.pop(); - if (isBool(a) && isBool(b)) { - stack.push(a || b); - } else { - stack.push(a | b); - } - break; - case 'pop': - stack.pop(); - break; - case 'roll': - b = stack.pop(); - a = stack.pop(); - stack.roll(a, b); - break; - case 'round': - a = stack.pop(); - stack.push(Math.round(a)); - break; - case 'sin': - a = stack.pop(); - stack.push(Math.sin(a)); - break; - case 'sqrt': - a = stack.pop(); - stack.push(Math.sqrt(a)); - break; - case 'sub': - b = stack.pop(); - a = stack.pop(); - stack.push(a - b); - break; - case 'true': - stack.push(true); - break; - case 'truncate': - a = stack.pop(); - a = a < 0 ? Math.ceil(a) : Math.floor(a); - stack.push(a); - break; - case 'xor': - b = stack.pop(); - a = stack.pop(); - if (isBool(a) && isBool(b)) { - stack.push(a != b); - } else { - stack.push(a ^ b); - } - break; - default: - error('Unknown operator ' + operator); - break; - } - } - return stack.stack; - } - }; - return PostScriptEvaluator; -})(); - - +var DEFAULT_ICON_SIZE = 22; // px var HIGHLIGHT_OFFSET = 4; // px var SUPPORTED_TYPES = ['Link', 'Text', 'Widget']; @@ -3034,7 +1618,7 @@ var Annotation = (function AnnotationClosure() { var data = this.data = {}; data.subtype = dict.get('Subtype').name; - var rect = dict.get('Rect'); + var rect = dict.get('Rect') || [0, 0, 0, 0]; data.rect = Util.normalizeRect(rect); data.annotationFlags = dict.get('F'); @@ -3058,23 +1642,30 @@ var Annotation = (function AnnotationClosure() { // TODO: implement proper support for annotations with line dash patterns. var dashArray = borderArray[3]; - if (data.borderWidth > 0 && dashArray && isArray(dashArray)) { - var dashArrayLength = dashArray.length; - if (dashArrayLength > 0) { - // According to the PDF specification: the elements in a dashArray - // shall be numbers that are nonnegative and not all equal to zero. - var isInvalid = false; - var numPositive = 0; - for (var i = 0; i < dashArrayLength; i++) { - if (!(+dashArray[i] >= 0)) { - isInvalid = true; - break; - } else if (dashArray[i] > 0) { - numPositive++; + if (data.borderWidth > 0 && dashArray) { + if (!isArray(dashArray)) { + // Ignore the border if dashArray is not actually an array, + // this is consistent with the behaviour in Adobe Reader. + data.borderWidth = 0; + } else { + var dashArrayLength = dashArray.length; + if (dashArrayLength > 0) { + // According to the PDF specification: the elements in a dashArray + // shall be numbers that are nonnegative and not all equal to zero. + var isInvalid = false; + var numPositive = 0; + for (var i = 0; i < dashArrayLength; i++) { + var validNumber = (+dashArray[i] >= 0); + if (!validNumber) { + isInvalid = true; + break; + } else if (dashArray[i] > 0) { + numPositive++; + } + } + if (isInvalid || numPositive === 0) { + data.borderWidth = 0; } - } - if (isInvalid || numPositive === 0) { - data.borderWidth = 0; } } } @@ -3082,6 +1673,7 @@ var Annotation = (function AnnotationClosure() { this.appearance = getDefaultAppearance(dict); data.hasAppearance = !!this.appearance; + data.id = params.ref.num; } Annotation.prototype = { @@ -3146,31 +1738,27 @@ var Annotation = (function AnnotationClosure() { data.rect); // rectangle is nessessary }, - loadResources: function(keys) { - var promise = new LegacyPromise(); - this.appearance.dict.getAsync('Resources').then(function(resources) { - if (!resources) { - promise.resolve(); - return; - } - var objectLoader = new ObjectLoader(resources.map, - keys, - resources.xref); - objectLoader.load().then(function() { - promise.resolve(resources); - }); + loadResources: function Annotation_loadResources(keys) { + return new Promise(function (resolve, reject) { + this.appearance.dict.getAsync('Resources').then(function (resources) { + if (!resources) { + resolve(); + return; + } + var objectLoader = new ObjectLoader(resources.map, + keys, + resources.xref); + objectLoader.load().then(function() { + resolve(resources); + }, reject); + }, reject); }.bind(this)); - - return promise; }, getOperatorList: function Annotation_getOperatorList(evaluator) { - var promise = new LegacyPromise(); - if (!this.appearance) { - promise.resolve(new OperatorList()); - return promise; + return Promise.resolve(new OperatorList()); } var data = this.data; @@ -3189,20 +1777,18 @@ var Annotation = (function AnnotationClosure() { var bbox = appearanceDict.get('BBox') || [0, 0, 1, 1]; var matrix = appearanceDict.get('Matrix') || [1, 0, 0, 1, 0 ,0]; var transform = getTransformMatrix(data.rect, bbox, matrix); + var self = this; - var border = data.border; - - resourcesPromise.then(function(resources) { - var opList = new OperatorList(); - opList.addOp(OPS.beginAnnotation, [data.rect, transform, matrix]); - evaluator.getOperatorList(this.appearance, resources, opList); - opList.addOp(OPS.endAnnotation, []); - promise.resolve(opList); - - this.appearance.reset(); - }.bind(this)); - - return promise; + return resourcesPromise.then(function(resources) { + var opList = new OperatorList(); + opList.addOp(OPS.beginAnnotation, [data.rect, transform, matrix]); + return evaluator.getOperatorList(self.appearance, resources, opList). + then(function () { + opList.addOp(OPS.endAnnotation, []); + self.appearance.reset(); + return opList; + }); + }); } }; @@ -3282,10 +1868,10 @@ var Annotation = (function AnnotationClosure() { annotations, opList, pdfManager, partialEvaluator, intent) { function reject(e) { - annotationsReadyPromise.reject(e); + annotationsReadyCapability.reject(e); } - var annotationsReadyPromise = new LegacyPromise(); + var annotationsReadyCapability = createPromiseCapability(); var annotationPromises = []; for (var i = 0, n = annotations.length; i < n; ++i) { @@ -3302,10 +1888,10 @@ var Annotation = (function AnnotationClosure() { opList.addOpList(annotOpList); } opList.addOp(OPS.endAnnotations, []); - annotationsReadyPromise.resolve(); + annotationsReadyCapability.resolve(); }, reject); - return annotationsReadyPromise; + return annotationsReadyCapability.promise; }; return Annotation; @@ -3332,7 +1918,7 @@ var WidgetAnnotation = (function WidgetAnnotationClosure() { var fieldType = Util.getInheritableProperty(dict, 'FT'); data.fieldType = isName(fieldType) ? fieldType.name : ''; data.fieldFlags = Util.getInheritableProperty(dict, 'Ff') || 0; - this.fieldResources = Util.getInheritableProperty(dict, 'DR') || new Dict(); + this.fieldResources = Util.getInheritableProperty(dict, 'DR') || Dict.empty; // Building the full field name by collecting the field and // its ancestors 'T' data and joining them using '.'. @@ -3417,7 +2003,6 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() { } - var parent = WidgetAnnotation.prototype; Util.inherit(TextWidgetAnnotation, WidgetAnnotation, { hasHtml: function TextWidgetAnnotation_hasHtml() { return !this.data.hasAppearance && !!this.data.fieldValue; @@ -3440,7 +2025,7 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() { var fontObj = item.fontRefName ? commonObjs.getData(item.fontRefName) : null; - var cssRules = setTextStyles(content, item, fontObj); + setTextStyles(content, item, fontObj); element.appendChild(content); @@ -3452,54 +2037,20 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() { return Annotation.prototype.getOperatorList.call(this, evaluator); } - var promise = new LegacyPromise(); var opList = new OperatorList(); var data = this.data; // Even if there is an appearance stream, ignore it. This is the // behaviour used by Adobe Reader. - - var defaultAppearance = data.defaultAppearance; - if (!defaultAppearance) { - promise.resolve(opList); - return promise; + if (!data.defaultAppearance) { + return Promise.resolve(opList); } - // Include any font resources found in the default appearance - - var stream = new Stream(stringToBytes(defaultAppearance)); - evaluator.getOperatorList(stream, this.fieldResources, opList); - var appearanceFnArray = opList.fnArray; - var appearanceArgsArray = opList.argsArray; - var fnArray = []; - var argsArray = []; - - // TODO(mack): Add support for stroke color - data.rgb = [0, 0, 0]; - // TODO THIS DOESN'T MAKE ANY SENSE SINCE THE fnArray IS EMPTY! - for (var i = 0, n = fnArray.length; i < n; ++i) { - var fnId = appearanceFnArray[i]; - var args = appearanceArgsArray[i]; - - if (fnId === OPS.setFont) { - data.fontRefName = args[0]; - var size = args[1]; - if (size < 0) { - data.fontDirection = -1; - data.fontSize = -size; - } else { - data.fontDirection = 1; - data.fontSize = size; - } - } else if (fnId === OPS.setFillRGBColor) { - data.rgb = args; - } else if (fnId === OPS.setFillGray) { - var rgbValue = args[0] * 255; - data.rgb = [rgbValue, rgbValue, rgbValue]; - } - } - promise.resolve(opList); - return promise; + var stream = new Stream(stringToBytes(data.defaultAppearance)); + return evaluator.getOperatorList(stream, this.fieldResources, opList). + then(function () { + return opList; + }); } }); @@ -3580,6 +2131,8 @@ var TextAnnotation = (function TextAnnotationClosure() { if (data.hasAppearance) { data.name = 'NoIcon'; } else { + data.rect[1] = data.rect[3] - DEFAULT_ICON_SIZE; + data.rect[2] = data.rect[0] + DEFAULT_ICON_SIZE; data.name = dict.has('Name') ? dict.get('Name').name : 'Note'; } @@ -3627,10 +2180,12 @@ var TextAnnotation = (function TextAnnotationClosure() { var content = document.createElement('div'); content.className = 'annotTextContent'; content.setAttribute('hidden', true); + + var i, ii; if (item.hasBgColor) { var color = item.color; var rgb = []; - for (var i = 0; i < 3; ++i) { + for (i = 0; i < 3; ++i) { // Enlighten the color (70%) var c = Math.round(color[i] * 255); rgb[i] = Math.round((255 - c) * 0.7) + c; @@ -3647,7 +2202,7 @@ var TextAnnotation = (function TextAnnotationClosure() { } else { var e = document.createElement('span'); var lines = item.content.split(/(?:\r\n?|\n)/); - for (var i = 0, ii = lines.length; i < ii; ++i) { + for (i = 0, ii = lines.length; i < ii; ++i) { var line = lines[i]; e.appendChild(document.createTextNode(line)); if (i < (ii - 1)) { @@ -3686,7 +2241,6 @@ var TextAnnotation = (function TextAnnotationClosure() { } }; - var self = this; image.addEventListener('click', function image_clickHandler() { toggleAnnotation(); }, false); @@ -3791,7 +2345,6 @@ var LinkAnnotation = (function LinkAnnotationClosure() { container.className = 'annotLink'; var item = this.data; - var rect = item.rect; container.style.borderColor = item.colorCssRgb; container.style.borderStyle = 'solid'; @@ -4048,7 +2601,7 @@ var ChunkedStream = (function ChunkedStreamClosure() { return this.numChunksLoaded === this.numChunks; }, - onReceiveData: function(begin, chunk) { + onReceiveData: function ChunkedStream_onReceiveData(begin, chunk) { var end = begin + chunk.byteLength; assert(begin % this.chunkSize === 0, 'Bad begin offset: ' + begin); @@ -4056,26 +2609,27 @@ var ChunkedStream = (function ChunkedStreamClosure() { // See ChunkedStream.moveStart() var length = this.bytes.length; assert(end % this.chunkSize === 0 || end === length, - 'Bad end offset: ' + end); + 'Bad end offset: ' + end); this.bytes.set(new Uint8Array(chunk), begin); var chunkSize = this.chunkSize; var beginChunk = Math.floor(begin / chunkSize); var endChunk = Math.floor((end - 1) / chunkSize) + 1; + var curChunk; - for (var chunk = beginChunk; chunk < endChunk; ++chunk) { - if (!(chunk in this.loadedChunks)) { - this.loadedChunks[chunk] = true; + for (curChunk = beginChunk; curChunk < endChunk; ++curChunk) { + if (!(curChunk in this.loadedChunks)) { + this.loadedChunks[curChunk] = true; ++this.numChunksLoaded; } } }, - onReceiveInitialData: function (data) { + onReceiveInitialData: function ChunkedStream_onReceiveInitialData(data) { this.bytes.set(data); this.initialDataLength = data.length; - var endChunk = this.end === data.length ? - this.numChunks : Math.floor(data.length / this.chunkSize); + var endChunk = (this.end === data.length ? + this.numChunks : Math.floor(data.length / this.chunkSize)); for (var i = 0; i < endChunk; i++) { this.loadedChunks[i] = true; ++this.numChunksLoaded; @@ -4102,13 +2656,14 @@ var ChunkedStream = (function ChunkedStreamClosure() { }, nextEmptyChunk: function ChunkedStream_nextEmptyChunk(beginChunk) { - for (var chunk = beginChunk, n = this.numChunks; chunk < n; ++chunk) { + var chunk, n; + for (chunk = beginChunk, n = this.numChunks; chunk < n; ++chunk) { if (!(chunk in this.loadedChunks)) { return chunk; } } // Wrap around to beginning - for (var chunk = 0; chunk < beginChunk; ++chunk) { + for (chunk = 0; chunk < beginChunk; ++chunk) { if (!(chunk in this.loadedChunks)) { return chunk; } @@ -4124,6 +2679,10 @@ var ChunkedStream = (function ChunkedStreamClosure() { return this.end - this.start; }, + get isEmpty() { + return this.length === 0; + }, + getByte: function ChunkedStream_getByte() { var pos = this.pos; if (pos >= this.end) { @@ -4133,6 +2692,20 @@ var ChunkedStream = (function ChunkedStreamClosure() { return this.bytes[this.pos++]; }, + getUint16: function ChunkedStream_getUint16() { + var b0 = this.getByte(); + var b1 = this.getByte(); + return (b0 << 8) + b1; + }, + + getInt32: function ChunkedStream_getInt32() { + var b0 = this.getByte(); + var b1 = this.getByte(); + var b2 = this.getByte(); + var b3 = this.getByte(); + return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3; + }, + // returns subarray of original buffer // should only be read getBytes: function ChunkedStream_getBytes(length) { @@ -4146,8 +2719,9 @@ var ChunkedStream = (function ChunkedStreamClosure() { } var end = pos + length; - if (end > strEnd) + if (end > strEnd) { end = strEnd; + } this.ensureRange(pos, end); this.pos = end; @@ -4166,8 +2740,9 @@ var ChunkedStream = (function ChunkedStreamClosure() { }, skip: function ChunkedStream_skip(n) { - if (!n) + if (!n) { n = 1; + } this.pos += n; }, @@ -4212,7 +2787,6 @@ var ChunkedStream = (function ChunkedStreamClosure() { var ChunkedStreamManager = (function ChunkedStreamManagerClosure() { function ChunkedStreamManager(length, chunkSize, url, args) { - var self = this; this.stream = new ChunkedStream(length, chunkSize, this); this.length = length; this.chunkSize = chunkSize; @@ -4250,7 +2824,8 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() { this.requestsByChunk = {}; this.callbacksByRequest = {}; - this.loadedStream = new LegacyPromise(); + this._loadedStreamCapability = createPromiseCapability(); + if (args.initialData) { this.setInitialData(args.initialData); } @@ -4261,7 +2836,7 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() { setInitialData: function ChunkedStreamManager_setInitialData(data) { this.stream.onReceiveInitialData(data); if (this.stream.allChunksLoaded()) { - this.loadedStream.resolve(this.stream); + this._loadedStreamCapability.resolve(this.stream); } else if (this.msgHandler) { this.msgHandler.send('DocProgress', { loaded: data.length, @@ -4271,7 +2846,7 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() { }, onLoadedStream: function ChunkedStreamManager_getLoadedStream() { - return this.loadedStream; + return this._loadedStreamCapability.promise; }, // Get all the chunks that are not yet loaded and groups them into @@ -4279,7 +2854,7 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() { requestAllChunks: function ChunkedStreamManager_requestAllChunks() { var missingChunks = this.stream.getMissingChunks(); this.requestChunks(missingChunks); - return this.loadedStream; + return this._loadedStreamCapability.promise; }, requestChunks: function ChunkedStreamManager_requestChunks(chunks, @@ -4287,8 +2862,9 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() { var requestId = this.currRequestId++; var chunksNeeded; + var i, ii; this.chunksNeededByRequest[requestId] = chunksNeeded = {}; - for (var i = 0, ii = chunks.length; i < ii; i++) { + for (i = 0, ii = chunks.length; i < ii; i++) { if (!this.stream.hasChunk(chunks[i])) { chunksNeeded[chunks[i]] = true; } @@ -4319,7 +2895,7 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() { var groupedChunksToRequest = this.groupChunks(chunksToRequest); - for (var i = 0; i < groupedChunksToRequest.length; ++i) { + for (i = 0; i < groupedChunksToRequest.length; ++i) { var groupedChunk = groupedChunksToRequest[i]; var begin = groupedChunk.beginChunk * this.chunkSize; var end = Math.min(groupedChunk.endChunk * this.chunkSize, this.length); @@ -4381,13 +2957,13 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() { } if (prevChunk >= 0 && prevChunk + 1 !== chunk) { - groupedChunks.push({ - beginChunk: beginChunk, endChunk: prevChunk + 1}); + groupedChunks.push({ beginChunk: beginChunk, + endChunk: prevChunk + 1 }); beginChunk = chunk; } if (i + 1 === chunks.length) { - groupedChunks.push({ - beginChunk: beginChunk, endChunk: chunk + 1}); + groupedChunks.push({ beginChunk: beginChunk, + endChunk: chunk + 1 }); } prevChunk = chunk; @@ -4396,8 +2972,8 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() { }, onProgress: function ChunkedStreamManager_onProgress(args) { - var bytesLoaded = this.stream.numChunksLoaded * this.chunkSize + - args.loaded; + var bytesLoaded = (this.stream.numChunksLoaded * this.chunkSize + + args.loaded); this.msgHandler.send('DocProgress', { loaded: bytesLoaded, total: this.length @@ -4414,18 +2990,18 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() { this.stream.onReceiveData(begin, chunk); if (this.stream.allChunksLoaded()) { - this.loadedStream.resolve(this.stream); + this._loadedStreamCapability.resolve(this.stream); } var loadedRequests = []; - for (var chunk = beginChunk; chunk < endChunk; ++chunk) { - + var i, requestId; + for (chunk = beginChunk; chunk < endChunk; ++chunk) { // The server might return more chunks than requested var requestIds = this.requestsByChunk[chunk] || []; delete this.requestsByChunk[chunk]; - for (var i = 0; i < requestIds.length; ++i) { - var requestId = requestIds[i]; + for (i = 0; i < requestIds.length; ++i) { + requestId = requestIds[i]; var chunksNeeded = this.chunksNeededByRequest[requestId]; if (chunk in chunksNeeded) { delete chunksNeeded[chunk]; @@ -4459,8 +3035,8 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() { } } - for (var i = 0; i < loadedRequests.length; ++i) { - var requestId = loadedRequests[i]; + for (i = 0; i < loadedRequests.length; ++i) { + requestId = loadedRequests[i]; var callback = this.callbacksByRequest[requestId]; delete this.callbacksByRequest[requestId]; if (callback) { @@ -4474,6 +3050,10 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() { }); }, + onError: function ChunkedStreamManager_onError(err) { + this._loadedStreamCapability.reject(err); + }, + getBeginChunk: function ChunkedStreamManager_getBeginChunk(begin) { var chunk = Math.floor(begin / this.chunkSize); return chunk; @@ -4513,24 +3093,24 @@ var BasePdfManager = (function BasePdfManagerClosure() { throw new NotImplementedException(); }, - ensureModel: function BasePdfManager_ensureModel(prop, args) { - return this.ensure(this.pdfModel, prop, args); + ensureDoc: function BasePdfManager_ensureDoc(prop, args) { + return this.ensure(this.pdfDocument, prop, args); }, ensureXRef: function BasePdfManager_ensureXRef(prop, args) { - return this.ensure(this.pdfModel.xref, prop, args); + return this.ensure(this.pdfDocument.xref, prop, args); }, ensureCatalog: function BasePdfManager_ensureCatalog(prop, args) { - return this.ensure(this.pdfModel.catalog, prop, args); + return this.ensure(this.pdfDocument.catalog, prop, args); }, getPage: function BasePdfManager_pagePage(pageIndex) { - return this.pdfModel.getPage(pageIndex); + return this.pdfDocument.getPage(pageIndex); }, cleanup: function BasePdfManager_cleanup() { - return this.pdfModel.cleanup(); + return this.pdfDocument.cleanup(); }, ensure: function BasePdfManager_ensure(obj, prop, args) { @@ -4546,12 +3126,17 @@ var BasePdfManager = (function BasePdfManagerClosure() { }, updatePassword: function BasePdfManager_updatePassword(password) { - this.pdfModel.xref.password = this.password = password; - if (this.passwordChangedPromise) { - this.passwordChangedPromise.resolve(); + this.pdfDocument.xref.password = this.password = password; + if (this._passwordChangedCapability) { + this._passwordChangedCapability.resolve(); } }, + passwordChanged: function BasePdfManager_passwordChanged() { + this._passwordChangedCapability = createPromiseCapability(); + return this._passwordChangedCapability.promise; + }, + terminate: function BasePdfManager_terminate() { return new NotImplementedException(); } @@ -4563,9 +3148,9 @@ var BasePdfManager = (function BasePdfManagerClosure() { var LocalPdfManager = (function LocalPdfManagerClosure() { function LocalPdfManager(data, password) { var stream = new Stream(data); - this.pdfModel = new PDFDocument(this, stream, password); - this.loadedStream = new LegacyPromise(); - this.loadedStream.resolve(stream); + this.pdfDocument = new PDFDocument(this, stream, password); + this._loadedStreamCapability = createPromiseCapability(); + this._loadedStreamCapability.resolve(stream); } LocalPdfManager.prototype = Object.create(BasePdfManager.prototype); @@ -4573,28 +3158,25 @@ var LocalPdfManager = (function LocalPdfManagerClosure() { LocalPdfManager.prototype.ensure = function LocalPdfManager_ensure(obj, prop, args) { - var promise = new LegacyPromise(); - try { - var value = obj[prop]; - var result; - if (typeof(value) === 'function') { - result = value.apply(obj, args); - } else { - result = value; + return new Promise(function (resolve, reject) { + try { + var value = obj[prop]; + var result; + if (typeof value === 'function') { + result = value.apply(obj, args); + } else { + result = value; + } + resolve(result); + } catch (e) { + reject(e); } - promise.resolve(result); - } catch (e) { - console.log(e.stack); - promise.reject(e); - } - return promise; + }); }; LocalPdfManager.prototype.requestRange = function LocalPdfManager_requestRange(begin, end) { - var promise = new LegacyPromise(); - promise.resolve(); - return promise; + return Promise.resolve(); }; LocalPdfManager.prototype.requestLoadedStream = @@ -4603,7 +3185,7 @@ var LocalPdfManager = (function LocalPdfManagerClosure() { LocalPdfManager.prototype.onLoadedStream = function LocalPdfManager_getLoadedStream() { - return this.loadedStream; + return this._loadedStreamCapability.promise; }; LocalPdfManager.prototype.terminate = @@ -4630,7 +3212,7 @@ var NetworkPdfManager = (function NetworkPdfManagerClosure() { this.streamManager = new ChunkedStreamManager(args.length, RANGE_CHUNK_SIZE, args.url, params); - this.pdfModel = new PDFDocument(this, this.streamManager.getStream(), + this.pdfDocument = new PDFDocument(this, this.streamManager.getStream(), args.password); } @@ -4639,42 +3221,39 @@ var NetworkPdfManager = (function NetworkPdfManagerClosure() { NetworkPdfManager.prototype.ensure = function NetworkPdfManager_ensure(obj, prop, args) { - var promise = new LegacyPromise(); - this.ensureHelper(promise, obj, prop, args); - return promise; - }; + var pdfManager = this; - NetworkPdfManager.prototype.ensureHelper = - function NetworkPdfManager_ensureHelper(promise, obj, prop, args) { - try { - var result; - var value = obj[prop]; - if (typeof(value) === 'function') { - result = value.apply(obj, args); - } else { - result = value; - } - promise.resolve(result); - } catch(e) { - if (!(e instanceof MissingDataException)) { - console.log(e.stack); - promise.reject(e); - return; + return new Promise(function (resolve, reject) { + function ensureHelper() { + try { + var result; + var value = obj[prop]; + if (typeof value === 'function') { + result = value.apply(obj, args); + } else { + result = value; + } + resolve(result); + } catch(e) { + if (!(e instanceof MissingDataException)) { + reject(e); + return; + } + pdfManager.streamManager.requestRange(e.begin, e.end, ensureHelper); + } } - this.streamManager.requestRange(e.begin, e.end, function() { - this.ensureHelper(promise, obj, prop, args); - }.bind(this)); - } + ensureHelper(); + }); }; NetworkPdfManager.prototype.requestRange = function NetworkPdfManager_requestRange(begin, end) { - var promise = new LegacyPromise(); - this.streamManager.requestRange(begin, end, function() { - promise.resolve(); - }); - return promise; + return new Promise(function (resolve) { + this.streamManager.requestRange(begin, end, function() { + resolve(); + }); + }.bind(this)); }; NetworkPdfManager.prototype.requestLoadedStream = @@ -4699,6 +3278,8 @@ var NetworkPdfManager = (function NetworkPdfManagerClosure() { var Page = (function PageClosure() { + var LETTER_SIZE_MEDIABOX = [0, 0, 612, 792]; + function Page(pdfManager, xref, pageIndex, pageDict, ref, fontCache) { this.pdfManager = pdfManager; this.pageIndex = pageIndex; @@ -4716,51 +3297,69 @@ var Page = (function PageClosure() { getPageProp: function Page_getPageProp(key) { return this.pageDict.get(key); }, - inheritPageProp: function Page_inheritPageProp(key) { + + getInheritedPageProp: function Page_inheritPageProp(key) { var dict = this.pageDict; - var obj = dict.get(key); - while (obj === undefined) { + var value = dict.get(key); + while (value === undefined) { dict = dict.get('Parent'); - if (!dict) + if (!dict) { break; - obj = dict.get(key); + } + value = dict.get(key); } - return obj; + return value; }, + get content() { return this.getPageProp('Contents'); }, + get resources() { - return shadow(this, 'resources', this.inheritPageProp('Resources')); + var value = this.getInheritedPageProp('Resources'); + // For robustness: The spec states that a \Resources entry has to be + // present, but can be empty. Some document omit it still. In this case + // return an empty dictionary: + if (value === undefined) { + value = Dict.empty; + } + return shadow(this, 'resources', value); }, + get mediaBox() { - var obj = this.inheritPageProp('MediaBox'); + var obj = this.getInheritedPageProp('MediaBox'); // Reset invalid media box to letter size. - if (!isArray(obj) || obj.length !== 4) - obj = [0, 0, 612, 792]; + if (!isArray(obj) || obj.length !== 4) { + obj = LETTER_SIZE_MEDIABOX; + } return shadow(this, 'mediaBox', obj); }, + get view() { var mediaBox = this.mediaBox; - var cropBox = this.inheritPageProp('CropBox'); - if (!isArray(cropBox) || cropBox.length !== 4) + var cropBox = this.getInheritedPageProp('CropBox'); + if (!isArray(cropBox) || cropBox.length !== 4) { return shadow(this, 'view', mediaBox); + } // From the spec, 6th ed., p.963: // "The crop, bleed, trim, and art boxes should not ordinarily // extend beyond the boundaries of the media box. If they do, they are // effectively reduced to their intersection with the media box." cropBox = Util.intersect(cropBox, mediaBox); - if (!cropBox) + if (!cropBox) { return shadow(this, 'view', mediaBox); - + } return shadow(this, 'view', cropBox); }, + get annotationRefs() { - return shadow(this, 'annotationRefs', this.inheritPageProp('Annots')); + return shadow(this, 'annotationRefs', + this.getInheritedPageProp('Annots')); }, + get rotate() { - var rotate = this.inheritPageProp('Rotate') || 0; + var rotate = this.getInheritedPageProp('Rotate') || 0; // Normalize rotation so it's a multiple of 90 and between 0 and 270 if (rotate % 90 !== 0) { rotate = 0; @@ -4773,6 +3372,7 @@ var Page = (function PageClosure() { } return shadow(this, 'rotate', rotate); }, + getContentStream: function Page_getContentStream() { var content = this.content; var stream; @@ -4781,8 +3381,9 @@ var Page = (function PageClosure() { var xref = this.xref; var i, n = content.length; var streams = []; - for (i = 0; i < n; ++i) + for (i = 0; i < n; ++i) { streams.push(xref.fetchIfRef(content[i])); + } stream = new StreamsSequenceStream(streams); } else if (isStream(content)) { stream = content; @@ -4792,31 +3393,22 @@ var Page = (function PageClosure() { } return stream; }, - loadResources: function(keys) { + + loadResources: function Page_loadResources(keys) { if (!this.resourcesPromise) { - // TODO: add async inheritPageProp and remove this. + // TODO: add async getInheritedPageProp and remove this. this.resourcesPromise = this.pdfManager.ensure(this, 'resources'); } - var promise = new LegacyPromise(); - this.resourcesPromise.then(function resourceSuccess() { + return this.resourcesPromise.then(function resourceSuccess() { var objectLoader = new ObjectLoader(this.resources.map, keys, this.xref); - objectLoader.load().then(function objectLoaderSuccess() { - promise.resolve(); - }); + return objectLoader.load(); }.bind(this)); - return promise; }, + getOperatorList: function Page_getOperatorList(handler, intent) { var self = this; - var promise = new LegacyPromise(); - - function reject(e) { - promise.reject(e); - } - - var pageListPromise = new LegacyPromise(); var pdfManager = this.pdfManager; var contentStreamPromise = pdfManager.ensure(this, 'getContentStream', @@ -4827,22 +3419,20 @@ var Page = (function PageClosure() { 'Pattern', 'Shading', 'XObject', - 'Font', + 'Font' // ProcSet // Properties ]); - var partialEvaluator = new PartialEvaluator( - pdfManager, this.xref, handler, - this.pageIndex, 'p' + this.pageIndex + '_', - this.idCounters, this.fontCache); + var partialEvaluator = new PartialEvaluator(pdfManager, this.xref, + handler, this.pageIndex, + 'p' + this.pageIndex + '_', + this.idCounters, + this.fontCache); - var dataPromises = Promise.all( - [contentStreamPromise, resourcesPromise], reject); - dataPromises.then(function(data) { + var dataPromises = Promise.all([contentStreamPromise, resourcesPromise]); + var pageListPromise = dataPromises.then(function(data) { var contentStream = data[0]; - - var opList = new OperatorList(intent, handler, self.pageIndex); handler.send('StartRenderPage', { @@ -4850,31 +3440,32 @@ var Page = (function PageClosure() { pageIndex: self.pageIndex, intent: intent }); - partialEvaluator.getOperatorList(contentStream, self.resources, opList); - pageListPromise.resolve(opList); + return partialEvaluator.getOperatorList(contentStream, self.resources, + opList).then(function () { + return opList; + }); }); var annotationsPromise = pdfManager.ensure(this, 'annotations'); - Promise.all([pageListPromise, annotationsPromise]).then(function(datas) { + return Promise.all([pageListPromise, annotationsPromise]).then( + function(datas) { var pageOpList = datas[0]; var annotations = datas[1]; if (annotations.length === 0) { pageOpList.flush(true); - promise.resolve(pageOpList); - return; + return pageOpList; } var annotationsReadyPromise = Annotation.appendToOperatorList( annotations, pageOpList, pdfManager, partialEvaluator, intent); - annotationsReadyPromise.then(function () { + return annotationsReadyPromise.then(function () { pageOpList.flush(true); - promise.resolve(pageOpList); - }, reject); - }, reject); - - return promise; + return pageOpList; + }); + }); }, + extractTextContent: function Page_extractTextContent() { var handler = { on: function nullHandlerOn() {}, @@ -4883,8 +3474,6 @@ var Page = (function PageClosure() { var self = this; - var textContentPromise = new LegacyPromise(); - var pdfManager = this.pdfManager; var contentStreamPromise = pdfManager.ensure(this, 'getContentStream', []); @@ -4897,19 +3486,17 @@ var Page = (function PageClosure() { var dataPromises = Promise.all([contentStreamPromise, resourcesPromise]); - dataPromises.then(function(data) { + return dataPromises.then(function(data) { var contentStream = data[0]; - var partialEvaluator = new PartialEvaluator( - pdfManager, self.xref, handler, - self.pageIndex, 'p' + self.pageIndex + '_', - self.idCounters, self.fontCache); + var partialEvaluator = new PartialEvaluator(pdfManager, self.xref, + handler, self.pageIndex, + 'p' + self.pageIndex + '_', + self.idCounters, + self.fontCache); - var bidiTexts = partialEvaluator.getTextContent(contentStream, - self.resources); - textContentPromise.resolve(bidiTexts); + return partialEvaluator.getTextContent(contentStream, + self.resources); }); - - return textContentPromise; }, getAnnotationsData: function Page_getAnnotationsData() { @@ -4923,7 +3510,7 @@ var Page = (function PageClosure() { get annotations() { var annotations = []; - var annotationRefs = this.annotationRefs || []; + var annotationRefs = (this.annotationRefs || []); for (var i = 0, n = annotationRefs.length; i < n; ++i) { var annotationRef = annotationRefs[i]; var annotation = Annotation.fromRef(this.xref, annotationRef); @@ -4947,16 +3534,17 @@ var Page = (function PageClosure() { */ var PDFDocument = (function PDFDocumentClosure() { function PDFDocument(pdfManager, arg, password) { - if (isStream(arg)) + if (isStream(arg)) { init.call(this, pdfManager, arg, password); - else if (isArrayBuffer(arg)) + } else if (isArrayBuffer(arg)) { init.call(this, pdfManager, new Stream(arg), password); - else + } else { error('PDFDocument: Unknown argument type'); + } } function init(pdfManager, stream, password) { - assertWellFormed(stream.length > 0, 'stream must have data'); + assert(stream.length > 0, 'stream must have data'); this.pdfManager = pdfManager; this.stream = stream; var xref = new XRef(this.stream, password, pdfManager); @@ -4967,16 +3555,18 @@ var PDFDocument = (function PDFDocumentClosure() { var pos = stream.pos; var end = stream.end; var strBuf = []; - if (pos + limit > end) + if (pos + limit > end) { limit = end - pos; + } for (var n = 0; n < limit; ++n) { strBuf.push(String.fromCharCode(stream.getByte())); } var str = strBuf.join(''); stream.pos = pos; var index = backwards ? str.lastIndexOf(needle) : str.indexOf(needle); - if (index == -1) + if (index == -1) { return false; /* not found */ + } stream.pos += index; return true; /* found */ } @@ -5049,16 +3639,18 @@ var PDFDocument = (function PDFDocumentClosure() { if (linearization) { // Find end of first obj. stream.reset(); - if (find(stream, 'endobj', 1024)) + if (find(stream, 'endobj', 1024)) { startXRef = stream.pos + 6; + } } else { // Find startxref by jumping backward from the end of the file. var step = 1024; var found = false, pos = stream.end; while (!found && pos > 0) { pos -= step - 'startxref'.length; - if (pos < 0) + if (pos < 0) { pos = 0; + } stream.pos = pos; found = find(stream, 'startxref', step, true); } @@ -5074,8 +3666,9 @@ var PDFDocument = (function PDFDocumentClosure() { ch = stream.getByte(); } startXRef = parseInt(str, 10); - if (isNaN(startXRef)) + if (isNaN(startXRef)) { startXRef = 0; + } } } // shadow the prototype getter with a data property @@ -5084,8 +3677,9 @@ var PDFDocument = (function PDFDocumentClosure() { get mainXRefEntriesOffset() { var mainXRefEntriesOffset = 0; var linearization = this.linearization; - if (linearization) + if (linearization) { mainXRefEntriesOffset = linearization.mainXRefEntriesOffset; + } // shadow the prototype getter with a data property return shadow(this, 'mainXRefEntriesOffset', mainXRefEntriesOffset); }, @@ -5146,8 +3740,8 @@ var PDFDocument = (function PDFDocumentClosure() { var value = infoDict.get(key); // Make sure the value conforms to the spec. if (validEntries[key](value)) { - docInfo[key] = typeof value !== 'string' ? value : - stringToPDFString(value); + docInfo[key] = (typeof value !== 'string' ? + value : stringToPDFString(value)); } else { info('Bad value in document info for "' + key + '"'); } @@ -5197,7 +3791,7 @@ var Name = (function NameClosure() { Name.get = function Name_get(name) { var nameValue = nameCache[name]; - return nameValue ? nameValue : (nameCache[name] = new Name(name)); + return (nameValue ? nameValue : (nameCache[name] = new Name(name))); }; return Name; @@ -5214,7 +3808,7 @@ var Cmd = (function CmdClosure() { Cmd.get = function Cmd_get(cmd) { var cmdValue = cmdCache[cmd]; - return cmdValue ? cmdValue : (cmdCache[cmd] = new Cmd(cmd)); + return (cmdValue ? cmdValue : (cmdCache[cmd] = new Cmd(cmd))); }; return Cmd; @@ -5225,11 +3819,30 @@ var Dict = (function DictClosure() { return nonSerializable; // creating closure on some variable }; + var GETALL_DICTIONARY_TYPES_WHITELIST = { + 'Background': true, + 'ExtGState': true, + 'Halftone': true, + 'Layout': true, + 'Mask': true, + 'Pagination': true, + 'Printing': true + }; + + function isRecursionAllowedFor(dict) { + if (!isName(dict.Type)) { + return true; + } + var dictType = dict.Type.name; + return GETALL_DICTIONARY_TYPES_WHITELIST[dictType] === true; + } + // xref is optional function Dict(xref) { // Map should only be used internally, use functions below to access. this.map = Object.create(null); this.xref = xref; + this.objId = null; this.__nonSerializable__ = nonSerializable; // disable cloning of the Dict } @@ -5257,33 +3870,26 @@ var Dict = (function DictClosure() { // Same as get(), but returns a promise and uses fetchIfRefAsync(). getAsync: function Dict_getAsync(key1, key2, key3) { var value; - var promise; var xref = this.xref; if (typeof (value = this.map[key1]) !== undefined || key1 in this.map || typeof key2 === undefined) { if (xref) { return xref.fetchIfRefAsync(value); } - promise = new LegacyPromise(); - promise.resolve(value); - return promise; + return Promise.resolve(value); } if (typeof (value = this.map[key2]) !== undefined || key2 in this.map || typeof key3 === undefined) { if (xref) { return xref.fetchIfRefAsync(value); } - promise = new LegacyPromise(); - promise.resolve(value); - return promise; + return Promise.resolve(value); } value = this.map[key3] || null; if (xref) { return xref.fetchIfRefAsync(value); } - promise = new LegacyPromise(); - promise.resolve(value); - return promise; + return Promise.resolve(value); }, // no dereferencing @@ -5293,10 +3899,52 @@ var Dict = (function DictClosure() { // creates new map and dereferences all Refs getAll: function Dict_getAll() { - var all = {}; - for (var key in this.map) { - var obj = this.get(key); - all[key] = obj instanceof Dict ? obj.getAll() : obj; + var all = Object.create(null); + var queue = null; + var key, obj; + for (key in this.map) { + obj = this.get(key); + if (obj instanceof Dict) { + if (isRecursionAllowedFor(obj)) { + (queue || (queue = [])).push({target: all, key: key, obj: obj}); + } else { + all[key] = this.getRaw(key); + } + } else { + all[key] = obj; + } + } + if (!queue) { + return all; + } + + // trying to take cyclic references into the account + var processed = Object.create(null); + while (queue.length > 0) { + var item = queue.shift(); + var itemObj = item.obj; + var objId = itemObj.objId; + if (objId && objId in processed) { + item.target[item.key] = processed[objId]; + continue; + } + var dereferenced = Object.create(null); + for (key in itemObj.map) { + obj = itemObj.get(key); + if (obj instanceof Dict) { + if (isRecursionAllowedFor(obj)) { + queue.push({target: dereferenced, key: key, obj: obj}); + } else { + dereferenced[key] = itemObj.getRaw(key); + } + } else { + dereferenced[key] = obj; + } + } + if (objId) { + processed[objId] = dereferenced; + } + item.target[item.key] = dereferenced; } return all; }, @@ -5316,6 +3964,8 @@ var Dict = (function DictClosure() { } }; + Dict.empty = new Dict(null); + return Dict; })(); @@ -5330,8 +3980,8 @@ var Ref = (function RefClosure() { return Ref; })(); -// The reference is identified by number and generation, -// this structure stores only one instance of the reference. +// The reference is identified by number and generation. +// This structure stores only one instance of the reference. var RefSet = (function RefSetClosure() { function RefSet() { this.dict = {}; @@ -5372,6 +4022,10 @@ var RefSetCache = (function RefSetCacheClosure() { this.dict['R' + ref.num + '.' + ref.gen] = obj; }, + putAlias: function RefSetCache_putAlias(ref, aliasRef) { + this.dict['R' + ref.num + '.' + ref.gen] = this.get(aliasRef); + }, + forEach: function RefSetCache_forEach(fn, thisArg) { for (var i in this.dict) { fn.call(thisArg, this.dict[i]); @@ -5392,7 +4046,7 @@ var Catalog = (function CatalogClosure() { this.xref = xref; this.catDict = xref.getCatalogObj(); this.fontCache = new RefSetCache(); - assertWellFormed(isDict(this.catDict), + assert(isDict(this.catDict), 'catalog object is not a dictionary'); this.pagePromises = []; @@ -5405,8 +4059,8 @@ var Catalog = (function CatalogClosure() { return shadow(this, 'metadata', null); } - var encryptMetadata = !this.xref.encrypt ? false : - this.xref.encrypt.encryptMetadata; + var encryptMetadata = (!this.xref.encrypt ? false : + this.xref.encrypt.encryptMetadata); var stream = this.xref.fetch(streamRef, !encryptMetadata); var metadata; @@ -5433,7 +4087,7 @@ var Catalog = (function CatalogClosure() { }, get toplevelPagesDict() { var pagesObj = this.catDict.get('Pages'); - assertWellFormed(isDict(pagesObj), 'invalid top-level pages dictionary'); + assert(isDict(pagesObj), 'invalid top-level pages dictionary'); // shadow the prototype getter return shadow(this, 'toplevelPagesDict', pagesObj); }, @@ -5503,11 +4157,11 @@ var Catalog = (function CatalogClosure() { } } } - return root.items.length > 0 ? root.items : null; + return (root.items.length > 0 ? root.items : null); }, get numPages() { var obj = this.toplevelPagesDict.get('Count'); - assertWellFormed( + assert( isInt(obj), 'page count in top level pages object is not an integer' ); @@ -5522,7 +4176,7 @@ var Catalog = (function CatalogClosure() { var xref = this.xref; var dests = {}, nameTreeRef, nameDictionaryRef; var obj = this.catDict.get('Names'); - if (obj) { + if (obj && obj.has('Dests')) { nameTreeRef = obj.getRaw('Dests'); } else if (this.catDict.has('Dests')) { nameDictionaryRef = this.catDict.get('Dests'); @@ -5550,6 +4204,30 @@ var Catalog = (function CatalogClosure() { } return shadow(this, 'destinations', dests); }, + get attachments() { + var xref = this.xref; + var attachments = null, nameTreeRef; + var obj = this.catDict.get('Names'); + if (obj) { + nameTreeRef = obj.getRaw('EmbeddedFiles'); + } + + if (nameTreeRef) { + var nameTree = new NameTree(nameTreeRef, xref); + var names = nameTree.getAll(); + for (var name in names) { + if (!names.hasOwnProperty(name)) { + continue; + } + var fs = new FileSpec(names[name], xref); + if (!attachments) { + attachments = {}; + } + attachments[stringToPDFString(name)] = fs.serializable; + } + } + return shadow(this, 'attachments', attachments); + }, get javaScript() { var xref = this.xref; var obj = this.catDict.get('Names'); @@ -5562,7 +4240,7 @@ var Catalog = (function CatalogClosure() { if (!names.hasOwnProperty(name)) { continue; } - // We don't really use the JavaScript right now so this code is + // We don't really use the JavaScript right now. This code is // defensive so we don't cause errors on document load. var jsDict = names[name]; if (!isDict(jsDict)) { @@ -5582,15 +4260,37 @@ var Catalog = (function CatalogClosure() { javaScript.push(stringToPDFString(js)); } } + + // Append OpenAction actions to javaScript array + var openactionDict = this.catDict.get('OpenAction'); + if (isDict(openactionDict)) { + var objType = openactionDict.get('Type'); + var actionType = openactionDict.get('S'); + var action = openactionDict.get('N'); + var isPrintAction = (isName(objType) && objType.name === 'Action' && + isName(actionType) && actionType.name === 'Named' && + isName(action) && action.name === 'Print'); + + if (isPrintAction) { + javaScript.push('print(true);'); + } + } + return shadow(this, 'javaScript', javaScript); }, cleanup: function Catalog_cleanup() { - this.fontCache.forEach(function (font) { - delete font.sent; - delete font.translated; + var promises = []; + this.fontCache.forEach(function (promise) { + promises.push(promise); }); - this.fontCache.clear(); + return Promise.all(promises).then(function (translatedFonts) { + for (var i = 0, ii = translatedFonts.length; i < ii; i++) { + var font = translatedFonts[i].dict; + delete font.translated; + } + this.fontCache.clear(); + }.bind(this)); }, getPage: function Catalog_getPage(pageIndex) { @@ -5608,7 +4308,7 @@ var Catalog = (function CatalogClosure() { }, getPageDict: function Catalog_getPageDict(pageIndex) { - var promise = new LegacyPromise(); + var capability = createPromiseCapability(); var nodesToVisit = [this.catDict.getRaw('Pages')]; var currentPageIndex = 0; var xref = this.xref; @@ -5621,7 +4321,7 @@ var Catalog = (function CatalogClosure() { xref.fetchAsync(currentNode).then(function (obj) { if ((isDict(obj, 'Page') || (isDict(obj) && !obj.has('Kids')))) { if (pageIndex === currentPageIndex) { - promise.resolve([obj, currentNode]); + capability.resolve([obj, currentNode]); } else { currentPageIndex++; next(); @@ -5630,7 +4330,7 @@ var Catalog = (function CatalogClosure() { } nodesToVisit.push(obj); next(); - }.bind(this), promise.reject.bind(promise)); + }.bind(this), capability.reject.bind(capability)); return; } @@ -5665,10 +4365,10 @@ var Catalog = (function CatalogClosure() { } } } - promise.reject('Page index ' + pageIndex + ' not found.'); + capability.reject('Page index ' + pageIndex + ' not found.'); } next(); - return promise; + return capability.promise; }, getPageIndex: function Catalog_getPageIndex(ref) { @@ -5698,7 +4398,7 @@ var Catalog = (function CatalogClosure() { var found = false; for (var i = 0; i < kids.length; i++) { var kid = kids[i]; - assert(isRef(kid), 'kids must be an ref'); + assert(isRef(kid), 'kids must be a ref'); if (kid.num == kidRef.num) { found = true; break; @@ -5743,7 +4443,6 @@ var Catalog = (function CatalogClosure() { var XRef = (function XRefClosure() { function XRef(stream, password) { - this.stream = stream; this.entries = []; this.xrefstms = {}; @@ -5773,8 +4472,8 @@ var XRef = (function XRefClosure() { if (encrypt) { var ids = trailerDict.get('ID'); var fileId = (ids && ids.length) ? ids[0] : ''; - this.encrypt = new CipherTransformFactory( - encrypt, fileId, this.password); + this.encrypt = new CipherTransformFactory(encrypt, fileId, + this.password); } // get the root dictionary (catalog) object @@ -5811,6 +4510,11 @@ var XRef = (function XRefClosure() { // The parser goes through the entire stream << ... >> and provides // a getter interface for the key-value table var dict = parser.getObj(); + + // The pdflib PDF generator can generate a nested trailer dictionary + if (!isDict(dict) && dict.dict) { + dict = dict.dict; + } if (!isDict(dict)) { error('Invalid XRef table: could not parse trailer dictionary'); } @@ -5865,16 +4569,17 @@ var XRef = (function XRefClosure() { entry.gen = parser.getObj(); var type = parser.getObj(); - if (isCmd(type, 'f')) + if (isCmd(type, 'f')) { entry.free = true; - else if (isCmd(type, 'n')) + } else if (isCmd(type, 'n')) { entry.uncompressed = true; + } // Validate entry obj if (!isInt(entry.offset) || !isInt(entry.gen) || !(entry.free || entry.uncompressed)) { console.log(entry.offset, entry.gen, entry.free, - entry.uncompressed); + entry.uncompressed); error('Invalid entry in XRef subsection: ' + first + ', ' + count); } @@ -5940,7 +4645,6 @@ var XRef = (function XRefClosure() { var entryRanges = streamState.entryRanges; while (entryRanges.length > 0) { - var first = entryRanges[0]; var n = entryRanges[1]; @@ -5956,9 +4660,10 @@ var XRef = (function XRefClosure() { streamState.streamPos = stream.pos; var type = 0, offset = 0, generation = 0; - for (j = 0; j < typeFieldWidth; ++j) + for (j = 0; j < typeFieldWidth; ++j) { type = (type << 8) | stream.getByte(); - // if type field is absent, its default value = 1 + } + // if type field is absent, its default value is 1 if (typeFieldWidth === 0) { type = 1; } @@ -6000,8 +4705,9 @@ var XRef = (function XRefClosure() { function readToken(data, offset) { var token = '', ch = data[offset]; while (ch !== 13 && ch !== 10) { - if (++offset >= data.length) + if (++offset >= data.length) { break; + } token += String.fromCharCode(ch); ch = data[offset]; } @@ -6016,9 +4722,9 @@ var XRef = (function XRefClosure() { while (i < length && data[offset + i] == what[i]) { ++i; } - if (i >= length) + if (i >= length) { break; // sequence found - + } offset++; skipped++; } @@ -6035,8 +4741,6 @@ var XRef = (function XRefClosure() { var buffer = stream.getBytes(); var position = stream.start, length = buffer.length; var trailers = [], xrefStms = []; - var state = 0; - var currentToken; while (position < length) { var ch = buffer[position]; if (ch === 32 || ch === 9 || ch === 13 || ch === 10) { @@ -6084,13 +4788,14 @@ var XRef = (function XRefClosure() { } } // reading XRef streams - for (var i = 0, ii = xrefStms.length; i < ii; ++i) { + var i, ii; + for (i = 0, ii = xrefStms.length; i < ii; ++i) { this.startXRefQueue.push(xrefStms[i]); this.readXRef(/* recoveryMode */ true); } // finding main trailer var dict; - for (var i = 0, ii = trailers.length; i < ii; ++i) { + for (i = 0, ii = trailers.length; i < ii; ++i) { stream.pos = trailers[i]; var parser = new Parser(new Lexer(stream), true, null); var obj = parser.getObj(); @@ -6130,7 +4835,6 @@ var XRef = (function XRefClosure() { // Get dictionary if (isCmd(obj, 'xref')) { - // Parse end-of-file XRef dict = this.processXRefTable(parser); if (!this.topDict) { @@ -6149,7 +4853,6 @@ var XRef = (function XRefClosure() { } } } else if (isInt(obj)) { - // Parse in-stream XRef if (!isInt(parser.getObj()) || !isCmd(parser.getObj(), 'obj') || @@ -6160,7 +4863,6 @@ var XRef = (function XRefClosure() { if (!this.topDict) { this.topDict = dict; } - if (!dict) { error('Failed to read XRef stream'); } @@ -6197,7 +4899,7 @@ var XRef = (function XRefClosure() { getEntry: function XRef_getEntry(i) { var xrefEntry = this.entries[i]; - if (xrefEntry !== null && !xrefEntry.free && xrefEntry.offset) { + if (xrefEntry && !xrefEntry.free && xrefEntry.offset) { return xrefEntry; } return null; @@ -6211,7 +4913,7 @@ var XRef = (function XRefClosure() { }, fetch: function XRef_fetch(ref, suppressEncryption) { - assertWellFormed(isRef(ref), 'ref object is not a reference'); + assert(isRef(ref), 'ref object is not a reference'); var num = ref.num; if (num in this.cache) { var cacheEntry = this.cache[num]; @@ -6226,14 +4928,18 @@ var XRef = (function XRefClosure() { } if (xrefEntry.uncompressed) { - return this.fetchUncompressed(ref, xrefEntry, suppressEncryption); + xrefEntry = this.fetchUncompressed(ref, xrefEntry, suppressEncryption); } else { - return this.fetchCompressed(xrefEntry, suppressEncryption); + xrefEntry = this.fetchCompressed(xrefEntry, suppressEncryption); + } + + if (isDict(xrefEntry)) { + xrefEntry.objId = 'R' + ref.num + '.' + ref.gen; } + return xrefEntry; }, - fetchUncompressed: function XRef_fetchUncompressed(ref, - xrefEntry, + fetchUncompressed: function XRef_fetchUncompressed(ref, xrefEntry, suppressEncryption) { var gen = ref.gen; var num = ref.num; @@ -6252,7 +4958,7 @@ var XRef = (function XRefClosure() { error('bad XRef entry'); } if (!isCmd(obj3, 'obj')) { - // some bad pdfs use "obj1234" and really mean 1234 + // some bad PDFs use "obj1234" and really mean 1234 if (obj3.cmd.indexOf('obj') === 0) { num = parseInt(obj3.cmd.substring(3), 10); if (!isNaN(num)) { @@ -6266,9 +4972,9 @@ var XRef = (function XRefClosure() { xrefEntry = parser.getObj(this.encrypt.createCipherTransform(num, gen)); } catch (ex) { - // almost all streams must be encrypted, but sometimes - // they are not probably due to some broken generators - // re-trying without encryption + // Almost all streams must be encrypted, but sometimes + // they are not, probably due to some broken generators. + // Retrying without encryption... return this.fetch(ref, true); } } else { @@ -6325,29 +5031,27 @@ var XRef = (function XRefClosure() { fetchIfRefAsync: function XRef_fetchIfRefAsync(obj) { if (!isRef(obj)) { - var promise = new LegacyPromise(); - promise.resolve(obj); - return promise; + return Promise.resolve(obj); } return this.fetchAsync(obj); }, fetchAsync: function XRef_fetchAsync(ref, suppressEncryption) { - var promise = new LegacyPromise(); - var tryFetch = function (promise) { - try { - promise.resolve(this.fetch(ref, suppressEncryption)); - } catch (e) { - if (e instanceof MissingDataException) { - this.stream.manager.requestRange(e.begin, e.end, tryFetch); - return; - } - promise.reject(e); - } - }.bind(this, promise); - tryFetch(); - return promise; - }, + return new Promise(function (resolve, reject) { + var tryFetch = function () { + try { + resolve(this.fetch(ref, suppressEncryption)); + } catch (e) { + if (e instanceof MissingDataException) { + this.stream.manager.requestRange(e.begin, e.end, tryFetch); + return; + } + reject(e); + } + }.bind(this); + tryFetch(); + }.bind(this)); + }, getCatalogObj: function XRef_getCatalogObj() { return this.root; @@ -6358,8 +5062,8 @@ var XRef = (function XRefClosure() { })(); /** - * A NameTree is like a Dict but has some adventagous properties, see the spec - * (7.9.6) for more details. + * A NameTree is like a Dict but has some advantageous properties, see the + * spec (7.9.6) for more details. * TODO: implement all the Dict functions and make this more efficent. */ var NameTree = (function NameTreeClosure() { @@ -6411,6 +5115,97 @@ var NameTree = (function NameTreeClosure() { })(); /** + * "A PDF file can refer to the contents of another file by using a File + * Specification (PDF 1.1)", see the spec (7.11) for more details. + * NOTE: Only embedded files are supported (as part of the attachments support) + * TODO: support the 'URL' file system (with caching if !/V), portable + * collections attributes and related files (/RF) + */ +var FileSpec = (function FileSpecClosure() { + function FileSpec(root, xref) { + if (!root || !isDict(root)) { + return; + } + this.xref = xref; + this.root = root; + if (root.has('FS')) { + this.fs = root.get('FS'); + } + this.description = root.has('Desc') ? + stringToPDFString(root.get('Desc')) : + ''; + if (root.has('RF')) { + warn('Related file specifications are not supported'); + } + this.contentAvailable = true; + if (!root.has('EF')) { + this.contentAvailable = false; + warn('Non-embedded file specifications are not supported'); + } + } + + function pickPlatformItem(dict) { + // Look for the filename in this order: + // UF, F, Unix, Mac, DOS + if (dict.has('UF')) { + return dict.get('UF'); + } else if (dict.has('F')) { + return dict.get('F'); + } else if (dict.has('Unix')) { + return dict.get('Unix'); + } else if (dict.has('Mac')) { + return dict.get('Mac'); + } else if (dict.has('DOS')) { + return dict.get('DOS'); + } else { + return null; + } + } + + FileSpec.prototype = { + get filename() { + if (!this._filename && this.root) { + var filename = pickPlatformItem(this.root) || 'unnamed'; + this._filename = stringToPDFString(filename). + replace(/\\\\/g, '\\'). + replace(/\\\//g, '/'). + replace(/\\/g, '/'); + } + return this._filename; + }, + get content() { + if (!this.contentAvailable) { + return null; + } + if (!this.contentRef && this.root) { + this.contentRef = pickPlatformItem(this.root.get('EF')); + } + var content = null; + if (this.contentRef) { + var xref = this.xref; + var fileObj = xref.fetchIfRef(this.contentRef); + if (fileObj && isStream(fileObj)) { + content = fileObj.getBytes(); + } else { + warn('Embedded file specification points to non-existing/invalid ' + + 'content'); + } + } else { + warn('Embedded file specification does not have a content'); + } + return content; + }, + get serializable() { + return { + filename: this.filename, + content: this.content + }; + } + }; + return FileSpec; +})(); + +/** * A helper for loading missing data in object graphs. It traverses the graph * depth first and queues up any objects that have missing data. Once it has * has traversed as many objects that are available it attempts to bundle the @@ -6422,12 +5217,12 @@ var NameTree = (function NameTreeClosure() { * entire PDF document object graph to be traversed. */ var ObjectLoader = (function() { - function mayHaveChildren(value) { return isRef(value) || isDict(value) || isArray(value) || isStream(value); } function addChildren(node, nodesToVisit) { + var value; if (isDict(node) || isStream(node)) { var map; if (isDict(node)) { @@ -6436,14 +5231,14 @@ var ObjectLoader = (function() { map = node.dict.map; } for (var key in map) { - var value = map[key]; + value = map[key]; if (mayHaveChildren(value)) { nodesToVisit.push(value); } } } else if (isArray(node)) { for (var i = 0, ii = node.length; i < ii; i++) { - var value = node[i]; + value = node[i]; if (mayHaveChildren(value)) { nodesToVisit.push(value); } @@ -6459,15 +5254,14 @@ var ObjectLoader = (function() { } ObjectLoader.prototype = { - load: function ObjectLoader_load() { var keys = this.keys; - this.promise = new LegacyPromise(); + this.capability = createPromiseCapability(); // Don't walk the graph if all the data is already loaded. if (!(this.xref.stream instanceof ChunkedStream) || this.xref.stream.getMissingChunks().length === 0) { - this.promise.resolve(); - return this.promise; + this.capability.resolve(); + return this.capability.promise; } this.refSet = new RefSet(); @@ -6478,7 +5272,7 @@ var ObjectLoader = (function() { } this.walk(nodesToVisit); - return this.promise; + return this.capability.promise; }, walk: function ObjectLoader_walk(nodesToVisit) { @@ -6545,9 +5339,8 @@ var ObjectLoader = (function() { } // Everything is loaded. this.refSet = null; - this.promise.resolve(); + this.capability.resolve(); } - }; return ObjectLoader; @@ -13587,6 +12380,1644 @@ var CIDToUnicodeMaps = { +var PDFFunction = (function PDFFunctionClosure() { + var CONSTRUCT_SAMPLED = 0; + var CONSTRUCT_INTERPOLATED = 2; + var CONSTRUCT_STICHED = 3; + var CONSTRUCT_POSTSCRIPT = 4; + + return { + getSampleArray: function PDFFunction_getSampleArray(size, outputSize, bps, + str) { + var i, ii; + var length = 1; + for (i = 0, ii = size.length; i < ii; i++) { + length *= size[i]; + } + length *= outputSize; + + var array = []; + var codeSize = 0; + var codeBuf = 0; + // 32 is a valid bps so shifting won't work + var sampleMul = 1.0 / (Math.pow(2.0, bps) - 1); + + var strBytes = str.getBytes((length * bps + 7) / 8); + var strIdx = 0; + for (i = 0; i < length; i++) { + while (codeSize < bps) { + codeBuf <<= 8; + codeBuf |= strBytes[strIdx++]; + codeSize += 8; + } + codeSize -= bps; + array.push((codeBuf >> codeSize) * sampleMul); + codeBuf &= (1 << codeSize) - 1; + } + return array; + }, + + getIR: function PDFFunction_getIR(xref, fn) { + var dict = fn.dict; + if (!dict) { + dict = fn; + } + + var types = [this.constructSampled, + null, + this.constructInterpolated, + this.constructStiched, + this.constructPostScript]; + + var typeNum = dict.get('FunctionType'); + var typeFn = types[typeNum]; + if (!typeFn) { + error('Unknown type of function'); + } + + return typeFn.call(this, fn, dict, xref); + }, + + fromIR: function PDFFunction_fromIR(IR) { + var type = IR[0]; + switch (type) { + case CONSTRUCT_SAMPLED: + return this.constructSampledFromIR(IR); + case CONSTRUCT_INTERPOLATED: + return this.constructInterpolatedFromIR(IR); + case CONSTRUCT_STICHED: + return this.constructStichedFromIR(IR); + //case CONSTRUCT_POSTSCRIPT: + default: + return this.constructPostScriptFromIR(IR); + } + }, + + parse: function PDFFunction_parse(xref, fn) { + var IR = this.getIR(xref, fn); + return this.fromIR(IR); + }, + + constructSampled: function PDFFunction_constructSampled(str, dict) { + function toMultiArray(arr) { + var inputLength = arr.length; + var out = []; + var index = 0; + for (var i = 0; i < inputLength; i += 2) { + out[index] = [arr[i], arr[i + 1]]; + ++index; + } + return out; + } + var domain = dict.get('Domain'); + var range = dict.get('Range'); + + if (!domain || !range) { + error('No domain or range'); + } + + var inputSize = domain.length / 2; + var outputSize = range.length / 2; + + domain = toMultiArray(domain); + range = toMultiArray(range); + + var size = dict.get('Size'); + var bps = dict.get('BitsPerSample'); + var order = dict.get('Order') || 1; + if (order !== 1) { + // No description how cubic spline interpolation works in PDF32000:2008 + // As in poppler, ignoring order, linear interpolation may work as good + info('No support for cubic spline interpolation: ' + order); + } + + var encode = dict.get('Encode'); + if (!encode) { + encode = []; + for (var i = 0; i < inputSize; ++i) { + encode.push(0); + encode.push(size[i] - 1); + } + } + encode = toMultiArray(encode); + + var decode = dict.get('Decode'); + if (!decode) { + decode = range; + } else { + decode = toMultiArray(decode); + } + + var samples = this.getSampleArray(size, outputSize, bps, str); + + return [ + CONSTRUCT_SAMPLED, inputSize, domain, encode, decode, samples, size, + outputSize, Math.pow(2, bps) - 1, range + ]; + }, + + constructSampledFromIR: function PDFFunction_constructSampledFromIR(IR) { + // See chapter 3, page 109 of the PDF reference + function interpolate(x, xmin, xmax, ymin, ymax) { + return ymin + ((x - xmin) * ((ymax - ymin) / (xmax - xmin))); + } + + return function constructSampledFromIRResult(args) { + // See chapter 3, page 110 of the PDF reference. + var m = IR[1]; + var domain = IR[2]; + var encode = IR[3]; + var decode = IR[4]; + var samples = IR[5]; + var size = IR[6]; + var n = IR[7]; + //var mask = IR[8]; + var range = IR[9]; + + if (m != args.length) { + error('Incorrect number of arguments: ' + m + ' != ' + + args.length); + } + + var x = args; + + // Building the cube vertices: its part and sample index + // http://rjwagner49.com/Mathematics/Interpolation.pdf + var cubeVertices = 1 << m; + var cubeN = new Float64Array(cubeVertices); + var cubeVertex = new Uint32Array(cubeVertices); + var i, j; + for (j = 0; j < cubeVertices; j++) { + cubeN[j] = 1; + } + + var k = n, pos = 1; + // Map x_i to y_j for 0 <= i < m using the sampled function. + for (i = 0; i < m; ++i) { + // x_i' = min(max(x_i, Domain_2i), Domain_2i+1) + var domain_2i = domain[i][0]; + var domain_2i_1 = domain[i][1]; + var xi = Math.min(Math.max(x[i], domain_2i), domain_2i_1); + + // e_i = Interpolate(x_i', Domain_2i, Domain_2i+1, + // Encode_2i, Encode_2i+1) + var e = interpolate(xi, domain_2i, domain_2i_1, + encode[i][0], encode[i][1]); + + // e_i' = min(max(e_i, 0), Size_i - 1) + var size_i = size[i]; + e = Math.min(Math.max(e, 0), size_i - 1); + + // Adjusting the cube: N and vertex sample index + var e0 = e < size_i - 1 ? Math.floor(e) : e - 1; // e1 = e0 + 1; + var n0 = e0 + 1 - e; // (e1 - e) / (e1 - e0); + var n1 = e - e0; // (e - e0) / (e1 - e0); + var offset0 = e0 * k; + var offset1 = offset0 + k; // e1 * k + for (j = 0; j < cubeVertices; j++) { + if (j & pos) { + cubeN[j] *= n1; + cubeVertex[j] += offset1; + } else { + cubeN[j] *= n0; + cubeVertex[j] += offset0; + } + } + + k *= size_i; + pos <<= 1; + } + + var y = new Float64Array(n); + for (j = 0; j < n; ++j) { + // Sum all cube vertices' samples portions + var rj = 0; + for (i = 0; i < cubeVertices; i++) { + rj += samples[cubeVertex[i] + j] * cubeN[i]; + } + + // r_j' = Interpolate(r_j, 0, 2^BitsPerSample - 1, + // Decode_2j, Decode_2j+1) + rj = interpolate(rj, 0, 1, decode[j][0], decode[j][1]); + + // y_j = min(max(r_j, range_2j), range_2j+1) + y[j] = Math.min(Math.max(rj, range[j][0]), range[j][1]); + } + + return y; + }; + }, + + constructInterpolated: function PDFFunction_constructInterpolated(str, + dict) { + var c0 = dict.get('C0') || [0]; + var c1 = dict.get('C1') || [1]; + var n = dict.get('N'); + + if (!isArray(c0) || !isArray(c1)) { + error('Illegal dictionary for interpolated function'); + } + + var length = c0.length; + var diff = []; + for (var i = 0; i < length; ++i) { + diff.push(c1[i] - c0[i]); + } + + return [CONSTRUCT_INTERPOLATED, c0, diff, n]; + }, + + constructInterpolatedFromIR: + function PDFFunction_constructInterpolatedFromIR(IR) { + var c0 = IR[1]; + var diff = IR[2]; + var n = IR[3]; + + var length = diff.length; + + return function constructInterpolatedFromIRResult(args) { + var x = n == 1 ? args[0] : Math.pow(args[0], n); + + var out = []; + for (var j = 0; j < length; ++j) { + out.push(c0[j] + (x * diff[j])); + } + + return out; + + }; + }, + + constructStiched: function PDFFunction_constructStiched(fn, dict, xref) { + var domain = dict.get('Domain'); + + if (!domain) { + error('No domain'); + } + + var inputSize = domain.length / 2; + if (inputSize != 1) { + error('Bad domain for stiched function'); + } + + var fnRefs = dict.get('Functions'); + var fns = []; + for (var i = 0, ii = fnRefs.length; i < ii; ++i) { + fns.push(PDFFunction.getIR(xref, xref.fetchIfRef(fnRefs[i]))); + } + + var bounds = dict.get('Bounds'); + var encode = dict.get('Encode'); + + return [CONSTRUCT_STICHED, domain, bounds, encode, fns]; + }, + + constructStichedFromIR: function PDFFunction_constructStichedFromIR(IR) { + var domain = IR[1]; + var bounds = IR[2]; + var encode = IR[3]; + var fnsIR = IR[4]; + var fns = []; + + for (var i = 0, ii = fnsIR.length; i < ii; i++) { + fns.push(PDFFunction.fromIR(fnsIR[i])); + } + + return function constructStichedFromIRResult(args) { + var clip = function constructStichedFromIRClip(v, min, max) { + if (v > max) { + v = max; + } else if (v < min) { + v = min; + } + return v; + }; + + // clip to domain + var v = clip(args[0], domain[0], domain[1]); + // calulate which bound the value is in + for (var i = 0, ii = bounds.length; i < ii; ++i) { + if (v < bounds[i]) { + break; + } + } + + // encode value into domain of function + var dmin = domain[0]; + if (i > 0) { + dmin = bounds[i - 1]; + } + var dmax = domain[1]; + if (i < bounds.length) { + dmax = bounds[i]; + } + + var rmin = encode[2 * i]; + var rmax = encode[2 * i + 1]; + + var v2 = rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin); + + // call the appropriate function + return fns[i]([v2]); + }; + }, + + constructPostScript: function PDFFunction_constructPostScript(fn, dict, + xref) { + var domain = dict.get('Domain'); + var range = dict.get('Range'); + + if (!domain) { + error('No domain.'); + } + + if (!range) { + error('No range.'); + } + + var lexer = new PostScriptLexer(fn); + var parser = new PostScriptParser(lexer); + var code = parser.parse(); + + return [CONSTRUCT_POSTSCRIPT, domain, range, code]; + }, + + constructPostScriptFromIR: function PDFFunction_constructPostScriptFromIR( + IR) { + var domain = IR[1]; + var range = IR[2]; + var code = IR[3]; + var numOutputs = range.length >> 1; + var numInputs = domain.length >> 1; + var evaluator = new PostScriptEvaluator(code); + // Cache the values for a big speed up, the cache size is limited though + // since the number of possible values can be huge from a PS function. + var cache = {}; + // The MAX_CACHE_SIZE is set to ~4x the maximum number of distinct values + // seen in our tests. + var MAX_CACHE_SIZE = 2048 * 4; + var cache_available = MAX_CACHE_SIZE; + return function constructPostScriptFromIRResult(args) { + var i, value; + var key = ''; + var input = new Array(numInputs); + for (i = 0; i < numInputs; i++) { + value = args[i]; + input[i] = value; + key += value + '_'; + } + + var cachedValue = cache[key]; + if (cachedValue !== undefined) { + return cachedValue; + } + + var output = new Array(numOutputs); + var stack = evaluator.execute(input); + var stackIndex = stack.length - numOutputs; + for (i = 0; i < numOutputs; i++) { + value = stack[stackIndex + i]; + var bound = range[i * 2]; + if (value < bound) { + value = bound; + } else { + bound = range[i * 2 +1]; + if (value > bound) { + value = bound; + } + } + output[i] = value; + } + if (cache_available > 0) { + cache_available--; + cache[key] = output; + } + return output; + }; + } + }; +})(); + +function isPDFFunction(v) { + var fnDict; + if (typeof v != 'object') { + return false; + } else if (isDict(v)) { + fnDict = v; + } else if (isStream(v)) { + fnDict = v.dict; + } else { + return false; + } + return fnDict.has('FunctionType'); +} + +var PostScriptStack = (function PostScriptStackClosure() { + var MAX_STACK_SIZE = 100; + function PostScriptStack(initialStack) { + this.stack = initialStack || []; + } + + PostScriptStack.prototype = { + push: function PostScriptStack_push(value) { + if (this.stack.length >= MAX_STACK_SIZE) { + error('PostScript function stack overflow.'); + } + this.stack.push(value); + }, + pop: function PostScriptStack_pop() { + if (this.stack.length <= 0) { + error('PostScript function stack underflow.'); + } + return this.stack.pop(); + }, + copy: function PostScriptStack_copy(n) { + if (this.stack.length + n >= MAX_STACK_SIZE) { + error('PostScript function stack overflow.'); + } + var stack = this.stack; + for (var i = stack.length - n, j = n - 1; j >= 0; j--, i++) { + stack.push(stack[i]); + } + }, + index: function PostScriptStack_index(n) { + this.push(this.stack[this.stack.length - n - 1]); + }, + // rotate the last n stack elements p times + roll: function PostScriptStack_roll(n, p) { + var stack = this.stack; + var l = stack.length - n; + var r = stack.length - 1, c = l + (p - Math.floor(p / n) * n), i, j, t; + for (i = l, j = r; i < j; i++, j--) { + t = stack[i]; stack[i] = stack[j]; stack[j] = t; + } + for (i = l, j = c - 1; i < j; i++, j--) { + t = stack[i]; stack[i] = stack[j]; stack[j] = t; + } + for (i = c, j = r; i < j; i++, j--) { + t = stack[i]; stack[i] = stack[j]; stack[j] = t; + } + } + }; + return PostScriptStack; +})(); +var PostScriptEvaluator = (function PostScriptEvaluatorClosure() { + function PostScriptEvaluator(operators) { + this.operators = operators; + } + PostScriptEvaluator.prototype = { + execute: function PostScriptEvaluator_execute(initialStack) { + var stack = new PostScriptStack(initialStack); + var counter = 0; + var operators = this.operators; + var length = operators.length; + var operator, a, b; + while (counter < length) { + operator = operators[counter++]; + if (typeof operator == 'number') { + // Operator is really an operand and should be pushed to the stack. + stack.push(operator); + continue; + } + switch (operator) { + // non standard ps operators + case 'jz': // jump if false + b = stack.pop(); + a = stack.pop(); + if (!a) { + counter = b; + } + break; + case 'j': // jump + a = stack.pop(); + counter = a; + break; + + // all ps operators in alphabetical order (excluding if/ifelse) + case 'abs': + a = stack.pop(); + stack.push(Math.abs(a)); + break; + case 'add': + b = stack.pop(); + a = stack.pop(); + stack.push(a + b); + break; + case 'and': + b = stack.pop(); + a = stack.pop(); + if (isBool(a) && isBool(b)) { + stack.push(a && b); + } else { + stack.push(a & b); + } + break; + case 'atan': + a = stack.pop(); + stack.push(Math.atan(a)); + break; + case 'bitshift': + b = stack.pop(); + a = stack.pop(); + if (a > 0) { + stack.push(a << b); + } else { + stack.push(a >> b); + } + break; + case 'ceiling': + a = stack.pop(); + stack.push(Math.ceil(a)); + break; + case 'copy': + a = stack.pop(); + stack.copy(a); + break; + case 'cos': + a = stack.pop(); + stack.push(Math.cos(a)); + break; + case 'cvi': + a = stack.pop() | 0; + stack.push(a); + break; + case 'cvr': + // noop + break; + case 'div': + b = stack.pop(); + a = stack.pop(); + stack.push(a / b); + break; + case 'dup': + stack.copy(1); + break; + case 'eq': + b = stack.pop(); + a = stack.pop(); + stack.push(a == b); + break; + case 'exch': + stack.roll(2, 1); + break; + case 'exp': + b = stack.pop(); + a = stack.pop(); + stack.push(Math.pow(a, b)); + break; + case 'false': + stack.push(false); + break; + case 'floor': + a = stack.pop(); + stack.push(Math.floor(a)); + break; + case 'ge': + b = stack.pop(); + a = stack.pop(); + stack.push(a >= b); + break; + case 'gt': + b = stack.pop(); + a = stack.pop(); + stack.push(a > b); + break; + case 'idiv': + b = stack.pop(); + a = stack.pop(); + stack.push((a / b) | 0); + break; + case 'index': + a = stack.pop(); + stack.index(a); + break; + case 'le': + b = stack.pop(); + a = stack.pop(); + stack.push(a <= b); + break; + case 'ln': + a = stack.pop(); + stack.push(Math.log(a)); + break; + case 'log': + a = stack.pop(); + stack.push(Math.log(a) / Math.LN10); + break; + case 'lt': + b = stack.pop(); + a = stack.pop(); + stack.push(a < b); + break; + case 'mod': + b = stack.pop(); + a = stack.pop(); + stack.push(a % b); + break; + case 'mul': + b = stack.pop(); + a = stack.pop(); + stack.push(a * b); + break; + case 'ne': + b = stack.pop(); + a = stack.pop(); + stack.push(a != b); + break; + case 'neg': + a = stack.pop(); + stack.push(-a); + break; + case 'not': + a = stack.pop(); + if (isBool(a)) { + stack.push(!a); + } else { + stack.push(~a); + } + break; + case 'or': + b = stack.pop(); + a = stack.pop(); + if (isBool(a) && isBool(b)) { + stack.push(a || b); + } else { + stack.push(a | b); + } + break; + case 'pop': + stack.pop(); + break; + case 'roll': + b = stack.pop(); + a = stack.pop(); + stack.roll(a, b); + break; + case 'round': + a = stack.pop(); + stack.push(Math.round(a)); + break; + case 'sin': + a = stack.pop(); + stack.push(Math.sin(a)); + break; + case 'sqrt': + a = stack.pop(); + stack.push(Math.sqrt(a)); + break; + case 'sub': + b = stack.pop(); + a = stack.pop(); + stack.push(a - b); + break; + case 'true': + stack.push(true); + break; + case 'truncate': + a = stack.pop(); + a = a < 0 ? Math.ceil(a) : Math.floor(a); + stack.push(a); + break; + case 'xor': + b = stack.pop(); + a = stack.pop(); + if (isBool(a) && isBool(b)) { + stack.push(a != b); + } else { + stack.push(a ^ b); + } + break; + default: + error('Unknown operator ' + operator); + break; + } + } + return stack.stack; + } + }; + return PostScriptEvaluator; +})(); + + +var ColorSpace = (function ColorSpaceClosure() { + // Constructor should define this.numComps, this.defaultColor, this.name + function ColorSpace() { + error('should not call ColorSpace constructor'); + } + + ColorSpace.prototype = { + /** + * Converts the color value to the RGB color. The color components are + * located in the src array starting from the srcOffset. Returns the array + * of the rgb components, each value ranging from [0,255]. + */ + getRgb: function ColorSpace_getRgb(src, srcOffset) { + var rgb = new Uint8Array(3); + this.getRgbItem(src, srcOffset, rgb, 0); + return rgb; + }, + /** + * Converts the color value to the RGB color, similar to the getRgb method. + * The result placed into the dest array starting from the destOffset. + */ + getRgbItem: function ColorSpace_getRgbItem(src, srcOffset, + dest, destOffset) { + error('Should not call ColorSpace.getRgbItem'); + }, + /** + * Converts the specified number of the color values to the RGB colors. + * The colors are located in the src array starting from the srcOffset. + * The result is placed into the dest array starting from the destOffset. + * The src array items shall be in [0,2^bits) range, the dest array items + * will be in [0,255] range. alpha01 indicates how many alpha components + * there are in the dest array; it will be either 0 (RGB array) or 1 (RGBA + * array). + */ + getRgbBuffer: function ColorSpace_getRgbBuffer(src, srcOffset, count, + dest, destOffset, bits, + alpha01) { + error('Should not call ColorSpace.getRgbBuffer'); + }, + /** + * Determines the number of bytes required to store the result of the + * conversion done by the getRgbBuffer method. As in getRgbBuffer, + * |alpha01| is either 0 (RGB output) or 1 (RGBA output). + */ + getOutputLength: function ColorSpace_getOutputLength(inputLength, + alpha01) { + error('Should not call ColorSpace.getOutputLength'); + }, + /** + * Returns true if source data will be equal the result/output data. + */ + isPassthrough: function ColorSpace_isPassthrough(bits) { + return false; + }, + /** + * Fills in the RGB colors in the destination buffer. alpha01 indicates + * how many alpha components there are in the dest array; it will be either + * 0 (RGB array) or 1 (RGBA array). + */ + fillRgb: function ColorSpace_fillRgb(dest, originalWidth, + originalHeight, width, height, + actualHeight, bpc, comps, alpha01) { + var count = originalWidth * originalHeight; + var rgbBuf = null; + var numComponentColors = 1 << bpc; + var needsResizing = originalHeight != height || originalWidth != width; + var i, ii; + + if (this.isPassthrough(bpc)) { + rgbBuf = comps; + } else if (this.numComps === 1 && count > numComponentColors && + this.name !== 'DeviceGray' && this.name !== 'DeviceRGB') { + // Optimization: create a color map when there is just one component and + // we are converting more colors than the size of the color map. We + // don't build the map if the colorspace is gray or rgb since those + // methods are faster than building a map. This mainly offers big speed + // ups for indexed and alternate colorspaces. + // + // TODO it may be worth while to cache the color map. While running + // testing I never hit a cache so I will leave that out for now (perhaps + // we are reparsing colorspaces too much?). + var allColors = bpc <= 8 ? new Uint8Array(numComponentColors) : + new Uint16Array(numComponentColors); + var key; + for (i = 0; i < numComponentColors; i++) { + allColors[i] = i; + } + var colorMap = new Uint8Array(numComponentColors * 3); + this.getRgbBuffer(allColors, 0, numComponentColors, colorMap, 0, bpc, + /* alpha01 = */ 0); + + var destPos, rgbPos; + if (!needsResizing) { + // Fill in the RGB values directly into |dest|. + destPos = 0; + for (i = 0; i < count; ++i) { + key = comps[i] * 3; + dest[destPos++] = colorMap[key]; + dest[destPos++] = colorMap[key + 1]; + dest[destPos++] = colorMap[key + 2]; + destPos += alpha01; + } + } else { + rgbBuf = new Uint8Array(count * 3); + rgbPos = 0; + for (i = 0; i < count; ++i) { + key = comps[i] * 3; + rgbBuf[rgbPos++] = colorMap[key]; + rgbBuf[rgbPos++] = colorMap[key + 1]; + rgbBuf[rgbPos++] = colorMap[key + 2]; + } + } + } else { + if (!needsResizing) { + // Fill in the RGB values directly into |dest|. + this.getRgbBuffer(comps, 0, width * actualHeight, dest, 0, bpc, + alpha01); + } else { + rgbBuf = new Uint8Array(count * 3); + this.getRgbBuffer(comps, 0, count, rgbBuf, 0, bpc, + /* alpha01 = */ 0); + } + } + + if (rgbBuf) { + if (needsResizing) { + PDFImage.resize(rgbBuf, bpc, 3, originalWidth, originalHeight, width, + height, dest, alpha01); + } else { + rgbPos = 0; + destPos = 0; + for (i = 0, ii = width * actualHeight; i < ii; i++) { + dest[destPos++] = rgbBuf[rgbPos++]; + dest[destPos++] = rgbBuf[rgbPos++]; + dest[destPos++] = rgbBuf[rgbPos++]; + destPos += alpha01; + } + } + } + }, + /** + * True if the colorspace has components in the default range of [0, 1]. + * This should be true for all colorspaces except for lab color spaces + * which are [0,100], [-128, 127], [-128, 127]. + */ + usesZeroToOneRange: true + }; + + ColorSpace.parse = function ColorSpace_parse(cs, xref, res) { + var IR = ColorSpace.parseToIR(cs, xref, res); + if (IR instanceof AlternateCS) { + return IR; + } + return ColorSpace.fromIR(IR); + }; + + ColorSpace.fromIR = function ColorSpace_fromIR(IR) { + var name = isArray(IR) ? IR[0] : IR; + var whitePoint, blackPoint; + + switch (name) { + case 'DeviceGrayCS': + return this.singletons.gray; + case 'DeviceRgbCS': + return this.singletons.rgb; + case 'DeviceCmykCS': + return this.singletons.cmyk; + case 'CalGrayCS': + whitePoint = IR[1].WhitePoint; + blackPoint = IR[1].BlackPoint; + var gamma = IR[1].Gamma; + return new CalGrayCS(whitePoint, blackPoint, gamma); + case 'PatternCS': + var basePatternCS = IR[1]; + if (basePatternCS) { + basePatternCS = ColorSpace.fromIR(basePatternCS); + } + return new PatternCS(basePatternCS); + case 'IndexedCS': + var baseIndexedCS = IR[1]; + var hiVal = IR[2]; + var lookup = IR[3]; + return new IndexedCS(ColorSpace.fromIR(baseIndexedCS), hiVal, lookup); + case 'AlternateCS': + var numComps = IR[1]; + var alt = IR[2]; + var tintFnIR = IR[3]; + + return new AlternateCS(numComps, ColorSpace.fromIR(alt), + PDFFunction.fromIR(tintFnIR)); + case 'LabCS': + whitePoint = IR[1].WhitePoint; + blackPoint = IR[1].BlackPoint; + var range = IR[1].Range; + return new LabCS(whitePoint, blackPoint, range); + default: + error('Unkown name ' + name); + } + return null; + }; + + ColorSpace.parseToIR = function ColorSpace_parseToIR(cs, xref, res) { + if (isName(cs)) { + var colorSpaces = res.get('ColorSpace'); + if (isDict(colorSpaces)) { + var refcs = colorSpaces.get(cs.name); + if (refcs) { + cs = refcs; + } + } + } + + cs = xref.fetchIfRef(cs); + var mode; + + if (isName(cs)) { + mode = cs.name; + this.mode = mode; + + switch (mode) { + case 'DeviceGray': + case 'G': + return 'DeviceGrayCS'; + case 'DeviceRGB': + case 'RGB': + return 'DeviceRgbCS'; + case 'DeviceCMYK': + case 'CMYK': + return 'DeviceCmykCS'; + case 'Pattern': + return ['PatternCS', null]; + default: + error('unrecognized colorspace ' + mode); + } + } else if (isArray(cs)) { + mode = cs[0].name; + this.mode = mode; + var numComps, params; + + switch (mode) { + case 'DeviceGray': + case 'G': + return 'DeviceGrayCS'; + case 'DeviceRGB': + case 'RGB': + return 'DeviceRgbCS'; + case 'DeviceCMYK': + case 'CMYK': + return 'DeviceCmykCS'; + case 'CalGray': + params = cs[1].getAll(); + return ['CalGrayCS', params]; + case 'CalRGB': + return 'DeviceRgbCS'; + case 'ICCBased': + var stream = xref.fetchIfRef(cs[1]); + var dict = stream.dict; + numComps = dict.get('N'); + if (numComps == 1) { + return 'DeviceGrayCS'; + } else if (numComps == 3) { + return 'DeviceRgbCS'; + } else if (numComps == 4) { + return 'DeviceCmykCS'; + } + break; + case 'Pattern': + var basePatternCS = cs[1]; + if (basePatternCS) { + basePatternCS = ColorSpace.parseToIR(basePatternCS, xref, res); + } + return ['PatternCS', basePatternCS]; + case 'Indexed': + case 'I': + var baseIndexedCS = ColorSpace.parseToIR(cs[1], xref, res); + var hiVal = cs[2] + 1; + var lookup = xref.fetchIfRef(cs[3]); + if (isStream(lookup)) { + lookup = lookup.getBytes(); + } + return ['IndexedCS', baseIndexedCS, hiVal, lookup]; + case 'Separation': + case 'DeviceN': + var name = cs[1]; + numComps = 1; + if (isName(name)) { + numComps = 1; + } else if (isArray(name)) { + numComps = name.length; + } + var alt = ColorSpace.parseToIR(cs[2], xref, res); + var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3])); + return ['AlternateCS', numComps, alt, tintFnIR]; + case 'Lab': + params = cs[1].getAll(); + return ['LabCS', params]; + default: + error('unimplemented color space object "' + mode + '"'); + } + } else { + error('unrecognized color space object: "' + cs + '"'); + } + return null; + }; + /** + * Checks if a decode map matches the default decode map for a color space. + * This handles the general decode maps where there are two values per + * component. e.g. [0, 1, 0, 1, 0, 1] for a RGB color. + * This does not handle Lab, Indexed, or Pattern decode maps since they are + * slightly different. + * @param {Array} decode Decode map (usually from an image). + * @param {Number} n Number of components the color space has. + */ + ColorSpace.isDefaultDecode = function ColorSpace_isDefaultDecode(decode, n) { + if (!decode) { + return true; + } + + if (n * 2 !== decode.length) { + warn('The decode map is not the correct length'); + return true; + } + for (var i = 0, ii = decode.length; i < ii; i += 2) { + if (decode[i] !== 0 || decode[i + 1] != 1) { + return false; + } + } + return true; + }; + + ColorSpace.singletons = { + get gray() { + return shadow(this, 'gray', new DeviceGrayCS()); + }, + get rgb() { + return shadow(this, 'rgb', new DeviceRgbCS()); + }, + get cmyk() { + return shadow(this, 'cmyk', new DeviceCmykCS()); + } + }; + + return ColorSpace; +})(); + +/** + * Alternate color space handles both Separation and DeviceN color spaces. A + * Separation color space is actually just a DeviceN with one color component. + * Both color spaces use a tinting function to convert colors to a base color + * space. + */ +var AlternateCS = (function AlternateCSClosure() { + function AlternateCS(numComps, base, tintFn) { + this.name = 'Alternate'; + this.numComps = numComps; + this.defaultColor = new Float32Array(numComps); + for (var i = 0; i < numComps; ++i) { + this.defaultColor[i] = 1; + } + this.base = base; + this.tintFn = tintFn; + } + + AlternateCS.prototype = { + getRgb: ColorSpace.prototype.getRgb, + getRgbItem: function AlternateCS_getRgbItem(src, srcOffset, + dest, destOffset) { + var baseNumComps = this.base.numComps; + var input = 'subarray' in src ? + src.subarray(srcOffset, srcOffset + this.numComps) : + Array.prototype.slice.call(src, srcOffset, srcOffset + this.numComps); + var tinted = this.tintFn(input); + this.base.getRgbItem(tinted, 0, dest, destOffset); + }, + getRgbBuffer: function AlternateCS_getRgbBuffer(src, srcOffset, count, + dest, destOffset, bits, + alpha01) { + var tinted; + var tintFn = this.tintFn; + var base = this.base; + var scale = 1 / ((1 << bits) - 1); + var baseNumComps = base.numComps; + var usesZeroToOneRange = base.usesZeroToOneRange; + var isPassthrough = (base.isPassthrough(8) || !usesZeroToOneRange) && + alpha01 === 0; + var pos = isPassthrough ? destOffset : 0; + var baseBuf = isPassthrough ? dest : new Uint8Array(baseNumComps * count); + var numComps = this.numComps; + + var scaled = new Float32Array(numComps); + var i, j; + if (usesZeroToOneRange) { + for (i = 0; i < count; i++) { + for (j = 0; j < numComps; j++) { + scaled[j] = src[srcOffset++] * scale; + } + tinted = tintFn(scaled); + for (j = 0; j < baseNumComps; j++) { + baseBuf[pos++] = tinted[j] * 255; + } + } + } else { + for (i = 0; i < count; i++) { + for (j = 0; j < numComps; j++) { + scaled[j] = src[srcOffset++] * scale; + } + tinted = tintFn(scaled); + base.getRgbItem(tinted, 0, baseBuf, pos); + pos += baseNumComps; + } + } + if (!isPassthrough) { + base.getRgbBuffer(baseBuf, 0, count, dest, destOffset, 8, alpha01); + } + }, + getOutputLength: function AlternateCS_getOutputLength(inputLength, + alpha01) { + return this.base.getOutputLength(inputLength * + this.base.numComps / this.numComps, + alpha01); + }, + isPassthrough: ColorSpace.prototype.isPassthrough, + fillRgb: ColorSpace.prototype.fillRgb, + isDefaultDecode: function AlternateCS_isDefaultDecode(decodeMap) { + return ColorSpace.isDefaultDecode(decodeMap, this.numComps); + }, + usesZeroToOneRange: true + }; + + return AlternateCS; +})(); + +var PatternCS = (function PatternCSClosure() { + function PatternCS(baseCS) { + this.name = 'Pattern'; + this.base = baseCS; + } + PatternCS.prototype = {}; + + return PatternCS; +})(); + +var IndexedCS = (function IndexedCSClosure() { + function IndexedCS(base, highVal, lookup) { + this.name = 'Indexed'; + this.numComps = 1; + this.defaultColor = new Uint8Array([0]); + this.base = base; + this.highVal = highVal; + + var baseNumComps = base.numComps; + var length = baseNumComps * highVal; + var lookupArray; + + if (isStream(lookup)) { + lookupArray = new Uint8Array(length); + var bytes = lookup.getBytes(length); + lookupArray.set(bytes); + } else if (isString(lookup)) { + lookupArray = new Uint8Array(length); + for (var i = 0; i < length; ++i) { + lookupArray[i] = lookup.charCodeAt(i); + } + } else if (lookup instanceof Uint8Array || lookup instanceof Array) { + lookupArray = lookup; + } else { + error('Unrecognized lookup table: ' + lookup); + } + this.lookup = lookupArray; + } + + IndexedCS.prototype = { + getRgb: ColorSpace.prototype.getRgb, + getRgbItem: function IndexedCS_getRgbItem(src, srcOffset, + dest, destOffset) { + var numComps = this.base.numComps; + var start = src[srcOffset] * numComps; + this.base.getRgbItem(this.lookup, start, dest, destOffset); + }, + getRgbBuffer: function IndexedCS_getRgbBuffer(src, srcOffset, count, + dest, destOffset, bits, + alpha01) { + var base = this.base; + var numComps = base.numComps; + var outputDelta = base.getOutputLength(numComps, alpha01); + var lookup = this.lookup; + + for (var i = 0; i < count; ++i) { + var lookupPos = src[srcOffset++] * numComps; + base.getRgbBuffer(lookup, lookupPos, 1, dest, destOffset, 8, alpha01); + destOffset += outputDelta; + } + }, + getOutputLength: function IndexedCS_getOutputLength(inputLength, alpha01) { + return this.base.getOutputLength(inputLength * this.base.numComps, + alpha01); + }, + isPassthrough: ColorSpace.prototype.isPassthrough, + fillRgb: ColorSpace.prototype.fillRgb, + isDefaultDecode: function IndexedCS_isDefaultDecode(decodeMap) { + // indexed color maps shouldn't be changed + return true; + }, + usesZeroToOneRange: true + }; + return IndexedCS; +})(); + +var DeviceGrayCS = (function DeviceGrayCSClosure() { + function DeviceGrayCS() { + this.name = 'DeviceGray'; + this.numComps = 1; + this.defaultColor = new Float32Array([0]); + } + + DeviceGrayCS.prototype = { + getRgb: ColorSpace.prototype.getRgb, + getRgbItem: function DeviceGrayCS_getRgbItem(src, srcOffset, + dest, destOffset) { + var c = (src[srcOffset] * 255) | 0; + c = c < 0 ? 0 : c > 255 ? 255 : c; + dest[destOffset] = dest[destOffset + 1] = dest[destOffset + 2] = c; + }, + getRgbBuffer: function DeviceGrayCS_getRgbBuffer(src, srcOffset, count, + dest, destOffset, bits, + alpha01) { + var scale = 255 / ((1 << bits) - 1); + var j = srcOffset, q = destOffset; + for (var i = 0; i < count; ++i) { + var c = (scale * src[j++]) | 0; + dest[q++] = c; + dest[q++] = c; + dest[q++] = c; + q += alpha01; + } + }, + getOutputLength: function DeviceGrayCS_getOutputLength(inputLength, + alpha01) { + return inputLength * (3 + alpha01); + }, + isPassthrough: ColorSpace.prototype.isPassthrough, + fillRgb: ColorSpace.prototype.fillRgb, + isDefaultDecode: function DeviceGrayCS_isDefaultDecode(decodeMap) { + return ColorSpace.isDefaultDecode(decodeMap, this.numComps); + }, + usesZeroToOneRange: true + }; + return DeviceGrayCS; +})(); + +var DeviceRgbCS = (function DeviceRgbCSClosure() { + function DeviceRgbCS() { + this.name = 'DeviceRGB'; + this.numComps = 3; + this.defaultColor = new Float32Array([0, 0, 0]); + } + DeviceRgbCS.prototype = { + getRgb: ColorSpace.prototype.getRgb, + getRgbItem: function DeviceRgbCS_getRgbItem(src, srcOffset, + dest, destOffset) { + var r = (src[srcOffset] * 255) | 0; + var g = (src[srcOffset + 1] * 255) | 0; + var b = (src[srcOffset + 2] * 255) | 0; + dest[destOffset] = r < 0 ? 0 : r > 255 ? 255 : r; + dest[destOffset + 1] = g < 0 ? 0 : g > 255 ? 255 : g; + dest[destOffset + 2] = b < 0 ? 0 : b > 255 ? 255 : b; + }, + getRgbBuffer: function DeviceRgbCS_getRgbBuffer(src, srcOffset, count, + dest, destOffset, bits, + alpha01) { + if (bits === 8 && alpha01 === 0) { + dest.set(src.subarray(srcOffset, srcOffset + count * 3), destOffset); + return; + } + var scale = 255 / ((1 << bits) - 1); + var j = srcOffset, q = destOffset; + for (var i = 0; i < count; ++i) { + dest[q++] = (scale * src[j++]) | 0; + dest[q++] = (scale * src[j++]) | 0; + dest[q++] = (scale * src[j++]) | 0; + q += alpha01; + } + }, + getOutputLength: function DeviceRgbCS_getOutputLength(inputLength, + alpha01) { + return (inputLength * (3 + alpha01) / 3) | 0; + }, + isPassthrough: function DeviceRgbCS_isPassthrough(bits) { + return bits == 8; + }, + fillRgb: ColorSpace.prototype.fillRgb, + isDefaultDecode: function DeviceRgbCS_isDefaultDecode(decodeMap) { + return ColorSpace.isDefaultDecode(decodeMap, this.numComps); + }, + usesZeroToOneRange: true + }; + return DeviceRgbCS; +})(); + +var DeviceCmykCS = (function DeviceCmykCSClosure() { + // The coefficients below was found using numerical analysis: the method of + // steepest descent for the sum((f_i - color_value_i)^2) for r/g/b colors, + // where color_value is the tabular value from the table of sampled RGB colors + // from CMYK US Web Coated (SWOP) colorspace, and f_i is the corresponding + // CMYK color conversion using the estimation below: + // f(A, B,.. N) = Acc+Bcm+Ccy+Dck+c+Fmm+Gmy+Hmk+Im+Jyy+Kyk+Ly+Mkk+Nk+255 + function convertToRgb(src, srcOffset, srcScale, dest, destOffset) { + var c = src[srcOffset + 0] * srcScale; + var m = src[srcOffset + 1] * srcScale; + var y = src[srcOffset + 2] * srcScale; + var k = src[srcOffset + 3] * srcScale; + + var r = + (c * (-4.387332384609988 * c + 54.48615194189176 * m + + 18.82290502165302 * y + 212.25662451639585 * k + + -285.2331026137004) + + m * (1.7149763477362134 * m - 5.6096736904047315 * y + + -17.873870861415444 * k - 5.497006427196366) + + y * (-2.5217340131683033 * y - 21.248923337353073 * k + + 17.5119270841813) + + k * (-21.86122147463605 * k - 189.48180835922747) + 255) | 0; + var g = + (c * (8.841041422036149 * c + 60.118027045597366 * m + + 6.871425592049007 * y + 31.159100130055922 * k + + -79.2970844816548) + + m * (-15.310361306967817 * m + 17.575251261109482 * y + + 131.35250912493976 * k - 190.9453302588951) + + y * (4.444339102852739 * y + 9.8632861493405 * k - 24.86741582555878) + + k * (-20.737325471181034 * k - 187.80453709719578) + 255) | 0; + var b = + (c * (0.8842522430003296 * c + 8.078677503112928 * m + + 30.89978309703729 * y - 0.23883238689178934 * k + + -14.183576799673286) + + m * (10.49593273432072 * m + 63.02378494754052 * y + + 50.606957656360734 * k - 112.23884253719248) + + y * (0.03296041114873217 * y + 115.60384449646641 * k + + -193.58209356861505) + + k * (-22.33816807309886 * k - 180.12613974708367) + 255) | 0; + + dest[destOffset] = r > 255 ? 255 : r < 0 ? 0 : r; + dest[destOffset + 1] = g > 255 ? 255 : g < 0 ? 0 : g; + dest[destOffset + 2] = b > 255 ? 255 : b < 0 ? 0 : b; + } + + function DeviceCmykCS() { + this.name = 'DeviceCMYK'; + this.numComps = 4; + this.defaultColor = new Float32Array([0, 0, 0, 1]); + } + DeviceCmykCS.prototype = { + getRgb: ColorSpace.prototype.getRgb, + getRgbItem: function DeviceCmykCS_getRgbItem(src, srcOffset, + dest, destOffset) { + convertToRgb(src, srcOffset, 1, dest, destOffset); + }, + getRgbBuffer: function DeviceCmykCS_getRgbBuffer(src, srcOffset, count, + dest, destOffset, bits, + alpha01) { + var scale = 1 / ((1 << bits) - 1); + for (var i = 0; i < count; i++) { + convertToRgb(src, srcOffset, scale, dest, destOffset); + srcOffset += 4; + destOffset += 3 + alpha01; + } + }, + getOutputLength: function DeviceCmykCS_getOutputLength(inputLength, + alpha01) { + return (inputLength / 4 * (3 + alpha01)) | 0; + }, + isPassthrough: ColorSpace.prototype.isPassthrough, + fillRgb: ColorSpace.prototype.fillRgb, + isDefaultDecode: function DeviceCmykCS_isDefaultDecode(decodeMap) { + return ColorSpace.isDefaultDecode(decodeMap, this.numComps); + }, + usesZeroToOneRange: true + }; + + return DeviceCmykCS; +})(); + +// +// CalGrayCS: Based on "PDF Reference, Sixth Ed", p.245 +// +var CalGrayCS = (function CalGrayCSClosure() { + function CalGrayCS(whitePoint, blackPoint, gamma) { + this.name = 'CalGray'; + this.numComps = 1; + this.defaultColor = new Float32Array([0]); + + if (!whitePoint) { + error('WhitePoint missing - required for color space CalGray'); + } + blackPoint = blackPoint || [0, 0, 0]; + gamma = gamma || 1; + + // Translate arguments to spec variables. + this.XW = whitePoint[0]; + this.YW = whitePoint[1]; + this.ZW = whitePoint[2]; + + this.XB = blackPoint[0]; + this.YB = blackPoint[1]; + this.ZB = blackPoint[2]; + + this.G = gamma; + + // Validate variables as per spec. + if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) { + error('Invalid WhitePoint components for ' + this.name + + ', no fallback available'); + } + + if (this.XB < 0 || this.YB < 0 || this.ZB < 0) { + info('Invalid BlackPoint for ' + this.name + ', falling back to default'); + this.XB = this.YB = this.ZB = 0; + } + + if (this.XB !== 0 || this.YB !== 0 || this.ZB !== 0) { + warn(this.name + ', BlackPoint: XB: ' + this.XB + ', YB: ' + this.YB + + ', ZB: ' + this.ZB + ', only default values are supported.'); + } + + if (this.G < 1) { + info('Invalid Gamma: ' + this.G + ' for ' + this.name + + ', falling back to default'); + this.G = 1; + } + } + + function convertToRgb(cs, src, srcOffset, dest, destOffset, scale) { + // A represents a gray component of a calibrated gray space. + // A <---> AG in the spec + var A = src[srcOffset] * scale; + var AG = Math.pow(A, cs.G); + + // Computes L as per spec. ( = cs.YW * AG ) + // Except if other than default BlackPoint values are used. + var L = cs.YW * AG; + // http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html, Ch 4. + // Convert values to rgb range [0, 255]. + var val = Math.max(295.8 * Math.pow(L, 0.333333333333333333) - 40.8, 0) | 0; + dest[destOffset] = val; + dest[destOffset + 1] = val; + dest[destOffset + 2] = val; + } + + CalGrayCS.prototype = { + getRgb: ColorSpace.prototype.getRgb, + getRgbItem: function CalGrayCS_getRgbItem(src, srcOffset, + dest, destOffset) { + convertToRgb(this, src, srcOffset, dest, destOffset, 1); + }, + getRgbBuffer: function CalGrayCS_getRgbBuffer(src, srcOffset, count, + dest, destOffset, bits, + alpha01) { + var scale = 1 / ((1 << bits) - 1); + + for (var i = 0; i < count; ++i) { + convertToRgb(this, src, srcOffset, dest, destOffset, scale); + srcOffset += 1; + destOffset += 3 + alpha01; + } + }, + getOutputLength: function CalGrayCS_getOutputLength(inputLength, alpha01) { + return inputLength * (3 + alpha01); + }, + isPassthrough: ColorSpace.prototype.isPassthrough, + fillRgb: ColorSpace.prototype.fillRgb, + isDefaultDecode: function CalGrayCS_isDefaultDecode(decodeMap) { + return ColorSpace.isDefaultDecode(decodeMap, this.numComps); + }, + usesZeroToOneRange: true + }; + return CalGrayCS; +})(); + +// +// LabCS: Based on "PDF Reference, Sixth Ed", p.250 +// +var LabCS = (function LabCSClosure() { + function LabCS(whitePoint, blackPoint, range) { + this.name = 'Lab'; + this.numComps = 3; + this.defaultColor = new Float32Array([0, 0, 0]); + + if (!whitePoint) { + error('WhitePoint missing - required for color space Lab'); + } + blackPoint = blackPoint || [0, 0, 0]; + range = range || [-100, 100, -100, 100]; + + // Translate args to spec variables + this.XW = whitePoint[0]; + this.YW = whitePoint[1]; + this.ZW = whitePoint[2]; + this.amin = range[0]; + this.amax = range[1]; + this.bmin = range[2]; + this.bmax = range[3]; + + // These are here just for completeness - the spec doesn't offer any + // formulas that use BlackPoint in Lab + this.XB = blackPoint[0]; + this.YB = blackPoint[1]; + this.ZB = blackPoint[2]; + + // Validate vars as per spec + if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) { + error('Invalid WhitePoint components, no fallback available'); + } + + if (this.XB < 0 || this.YB < 0 || this.ZB < 0) { + info('Invalid BlackPoint, falling back to default'); + this.XB = this.YB = this.ZB = 0; + } + + if (this.amin > this.amax || this.bmin > this.bmax) { + info('Invalid Range, falling back to defaults'); + this.amin = -100; + this.amax = 100; + this.bmin = -100; + this.bmax = 100; + } + } + + // Function g(x) from spec + function fn_g(x) { + if (x >= 6 / 29) { + return x * x * x; + } else { + return (108 / 841) * (x - 4 / 29); + } + } + + function decode(value, high1, low2, high2) { + return low2 + (value) * (high2 - low2) / (high1); + } + + // If decoding is needed maxVal should be 2^bits per component - 1. + function convertToRgb(cs, src, srcOffset, maxVal, dest, destOffset) { + // XXX: Lab input is in the range of [0, 100], [amin, amax], [bmin, bmax] + // not the usual [0, 1]. If a command like setFillColor is used the src + // values will already be within the correct range. However, if we are + // converting an image we have to map the values to the correct range given + // above. + // Ls,as,bs <---> L*,a*,b* in the spec + var Ls = src[srcOffset]; + var as = src[srcOffset + 1]; + var bs = src[srcOffset + 2]; + if (maxVal !== false) { + Ls = decode(Ls, maxVal, 0, 100); + as = decode(as, maxVal, cs.amin, cs.amax); + bs = decode(bs, maxVal, cs.bmin, cs.bmax); + } + + // Adjust limits of 'as' and 'bs' + as = as > cs.amax ? cs.amax : as < cs.amin ? cs.amin : as; + bs = bs > cs.bmax ? cs.bmax : bs < cs.bmin ? cs.bmin : bs; + + // Computes intermediate variables X,Y,Z as per spec + var M = (Ls + 16) / 116; + var L = M + (as / 500); + var N = M - (bs / 200); + + var X = cs.XW * fn_g(L); + var Y = cs.YW * fn_g(M); + var Z = cs.ZW * fn_g(N); + + var r, g, b; + // Using different conversions for D50 and D65 white points, + // per http://www.color.org/srgb.pdf + if (cs.ZW < 1) { + // Assuming D50 (X=0.9642, Y=1.00, Z=0.8249) + r = X * 3.1339 + Y * -1.6170 + Z * -0.4906; + g = X * -0.9785 + Y * 1.9160 + Z * 0.0333; + b = X * 0.0720 + Y * -0.2290 + Z * 1.4057; + } else { + // Assuming D65 (X=0.9505, Y=1.00, Z=1.0888) + r = X * 3.2406 + Y * -1.5372 + Z * -0.4986; + g = X * -0.9689 + Y * 1.8758 + Z * 0.0415; + b = X * 0.0557 + Y * -0.2040 + Z * 1.0570; + } + // clamp color values to [0,1] range then convert to [0,255] range. + dest[destOffset] = r <= 0 ? 0 : r >= 1 ? 255 : Math.sqrt(r) * 255 | 0; + dest[destOffset + 1] = g <= 0 ? 0 : g >= 1 ? 255 : Math.sqrt(g) * 255 | 0; + dest[destOffset + 2] = b <= 0 ? 0 : b >= 1 ? 255 : Math.sqrt(b) * 255 | 0; + } + + LabCS.prototype = { + getRgb: ColorSpace.prototype.getRgb, + getRgbItem: function LabCS_getRgbItem(src, srcOffset, dest, destOffset) { + convertToRgb(this, src, srcOffset, false, dest, destOffset); + }, + getRgbBuffer: function LabCS_getRgbBuffer(src, srcOffset, count, + dest, destOffset, bits, + alpha01) { + var maxVal = (1 << bits) - 1; + for (var i = 0; i < count; i++) { + convertToRgb(this, src, srcOffset, maxVal, dest, destOffset); + srcOffset += 3; + destOffset += 3 + alpha01; + } + }, + getOutputLength: function LabCS_getOutputLength(inputLength, alpha01) { + return (inputLength * (3 + alpha01) / 3) | 0; + }, + isPassthrough: ColorSpace.prototype.isPassthrough, + isDefaultDecode: function LabCS_isDefaultDecode(decodeMap) { + // XXX: Decoding is handled with the lab conversion because of the strange + // ranges that are used. + return true; + }, + usesZeroToOneRange: false + }; + return LabCS; +})(); + + + var ARCFourCipher = (function ARCFourCipherClosure() { function ARCFourCipher(key) { this.a = 0; @@ -14073,7 +14504,7 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() { hashData[i++] = fileId[j]; } cipher = new ARCFourCipher(encryptionKey); - var checkData = cipher.encryptBlock(calculateMD5(hashData, 0, i)); + checkData = cipher.encryptBlock(calculateMD5(hashData, 0, i)); n = encryptionKey.length; var derivedKey = new Uint8Array(n), k; for (j = 1; j <= 19; ++j) { @@ -14412,15 +14843,16 @@ Shadings.RadialAxial = (function RadialAxialClosure() { return; } + var rgbColor; for (var i = t0; i <= t1; i += step) { - var rgbColor = cs.getRgb(fn([i]), 0); + rgbColor = cs.getRgb(fn([i]), 0); var cssColor = Util.makeCssRgb(rgbColor); colorStops.push([(i - t0) / diff, cssColor]); } var background = 'transparent'; if (dict.has('Background')) { - var rgbColor = cs.getRgb(dict.get('Background'), 0); + rgbColor = cs.getRgb(dict.get('Background'), 0); background = Util.makeCssRgb(rgbColor); } @@ -14610,7 +15042,6 @@ Shadings.Mesh = (function MeshClosure() { function decodeType5Shading(mesh, reader, verticesPerRow) { var coords = mesh.coords; var colors = mesh.colors; - var operators = []; var ps = []; // not maintaining cs since that will match ps while (reader.hasData) { var coord = reader.readCoordinate(); @@ -14920,6 +15351,38 @@ Shadings.Mesh = (function MeshClosure() { mesh.bounds = [minX, minY, maxX, maxY]; } + function packData(mesh) { + var i, ii, j, jj; + + var coords = mesh.coords; + var coordsPacked = new Float32Array(coords.length * 2); + for (i = 0, j = 0, ii = coords.length; i < ii; i++) { + var xy = coords[i]; + coordsPacked[j++] = xy[0]; + coordsPacked[j++] = xy[1]; + } + mesh.coords = coordsPacked; + + var colors = mesh.colors; + var colorsPacked = new Uint8Array(colors.length * 3); + for (i = 0, j = 0, ii = colors.length; i < ii; i++) { + var c = colors[i]; + colorsPacked[j++] = c[0]; + colorsPacked[j++] = c[1]; + colorsPacked[j++] = c[2]; + } + mesh.colors = colorsPacked; + + var figures = mesh.figures; + for (i = 0, ii = figures.length; i < ii; i++) { + var figure = figures[i], ps = figure.coords, cs = figure.colors; + for (j = 0, jj = ps.length; j < jj; j++) { + ps[j] *= 2; + cs[j] *= 3; + } + } + } + function Mesh(stream, matrix, xref, res) { assert(isStream(stream), 'Mesh data is not a stream'); var dict = stream.dict; @@ -15007,35 +15470,14 @@ Shadings.Mesh = (function MeshClosure() { } // calculate bounds updateBounds(this); + + packData(this); } Mesh.prototype = { getIR: function Mesh_getIR() { - var type = this.shadingType; - var i, ii, j; - var coords = this.coords; - var coordsPacked = new Float32Array(coords.length * 2); - for (i = 0, j = 0, ii = coords.length; i < ii; i++) { - var xy = coords[i]; - coordsPacked[j++] = xy[0]; - coordsPacked[j++] = xy[1]; - } - var colors = this.colors; - var colorsPacked = new Uint8Array(colors.length * 3); - for (i = 0, j = 0, ii = colors.length; i < ii; i++) { - var c = colors[i]; - colorsPacked[j++] = c[0]; - colorsPacked[j++] = c[1]; - colorsPacked[j++] = c[2]; - } - var figures = this.figures; - var bbox = this.bbox; - var bounds = this.bounds; - var matrix = this.matrix; - var background = this.background; - - return ['Mesh', type, coordsPacked, colorsPacked, figures, bounds, - matrix, bbox, background]; + return ['Mesh', this.shadingType, this.coords, this.colors, this.figures, + this.bounds, this.matrix, this.bbox, this.background]; } }; @@ -15073,9 +15515,6 @@ function getTilingPatternIR(operatorList, dict, args) { var PartialEvaluator = (function PartialEvaluatorClosure() { function PartialEvaluator(pdfManager, xref, handler, pageIndex, uniquePrefix, idCounters, fontCache) { - this.state = new EvalState(); - this.stateStack = []; - this.pdfManager = pdfManager; this.xref = xref; this.handler = handler; @@ -15085,6 +15524,28 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { this.fontCache = fontCache; } + // Trying to minimize Date.now() usage and check every 100 time + var TIME_SLOT_DURATION_MS = 20; + var CHECK_TIME_EVERY = 100; + function TimeSlotManager() { + this.reset(); + } + TimeSlotManager.prototype = { + check: function TimeSlotManager_check() { + if (++this.checked < CHECK_TIME_EVERY) { + return false; + } + this.checked = 0; + return this.endTime <= Date.now(); + }, + reset: function TimeSlotManager_reset() { + this.endTime = Date.now() + TIME_SLOT_DURATION_MS; + this.checked = 0; + } + }; + + var deferred = Promise.resolve(); + var TILING_PATTERN = 1, SHADING_PATTERN = 2; PartialEvaluator.prototype = { @@ -15093,14 +15554,20 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { return false; } + var processed = Object.create(null); + if (resources.objId) { + processed[resources.objId] = true; + } + var nodes = [resources]; while (nodes.length) { + var key; var node = nodes.shift(); // First check the current resources for blend modes. var graphicStates = node.get('ExtGState'); if (isDict(graphicStates)) { graphicStates = graphicStates.getAll(); - for (var key in graphicStates) { + for (key in graphicStates) { var graphicState = graphicStates[key]; var bm = graphicState['BM']; if (isName(bm) && bm.name !== 'Normal') { @@ -15114,16 +15581,19 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { continue; } xObjects = xObjects.getAll(); - for (var key in xObjects) { + for (key in xObjects) { var xObject = xObjects[key]; if (!isStream(xObject)) { continue; } var xResources = xObject.dict.get('Resources'); - // Only add the resource if it's different from the current one, - // otherwise we can get stuck in an infinite loop. - if (isDict(xResources) && xResources !== node) { + // Checking objId to detect an infinite loop. + if (isDict(xResources) && + (!xResources.objId || !processed[xResources.objId])) { nodes.push(xResources); + if (xResources.objId) { + processed[xResources.objId] = true; + } } } } @@ -15133,7 +15603,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { buildFormXObject: function PartialEvaluator_buildFormXObject(resources, xobj, smask, operatorList, - state) { + initialState) { var matrix = xobj.dict.get('Matrix'); var bbox = xobj.dict.get('BBox'); var group = xobj.dict.get('Group'); @@ -15147,43 +15617,57 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { }; var groupSubtype = group.get('S'); + var colorSpace; if (isName(groupSubtype) && groupSubtype.name === 'Transparency') { - groupOptions.isolated = group.get('I') || false; - groupOptions.knockout = group.get('K') || false; - var colorSpace = group.get('CS'); - groupOptions.colorSpace = colorSpace ? - ColorSpace.parseToIR(colorSpace, this.xref, resources) : null; + groupOptions.isolated = (group.get('I') || false); + groupOptions.knockout = (group.get('K') || false); + colorSpace = (group.has('CS') ? + ColorSpace.parse(group.get('CS'), this.xref, resources) : null); } + + if (smask && smask.backdrop) { + colorSpace = colorSpace || ColorSpace.singletons.rgb; + smask.backdrop = colorSpace.getRgb(smask.backdrop, 0); + } + operatorList.addOp(OPS.beginGroup, [groupOptions]); } operatorList.addOp(OPS.paintFormXObjectBegin, [matrix, bbox]); - this.getOperatorList(xobj, xobj.dict.get('Resources') || resources, - operatorList, state); - operatorList.addOp(OPS.paintFormXObjectEnd, []); + return this.getOperatorList(xobj, + (xobj.dict.get('Resources') || resources), operatorList, initialState). + then(function () { + operatorList.addOp(OPS.paintFormXObjectEnd, []); - if (group) { - operatorList.addOp(OPS.endGroup, [groupOptions]); - } + if (group) { + operatorList.addOp(OPS.endGroup, [groupOptions]); + } + }); }, - buildPaintImageXObject: function PartialEvaluator_buildPaintImageXObject( - resources, image, inline, operatorList, - cacheKey, cache) { + buildPaintImageXObject: + function PartialEvaluator_buildPaintImageXObject(resources, image, + inline, operatorList, + cacheKey, cache) { var self = this; var dict = image.dict; var w = dict.get('Width', 'W'); var h = dict.get('Height', 'H'); + if (!(w && isNum(w)) || !(h && isNum(h))) { + warn('Image dimensions are missing, or not numbers.'); + return; + } if (PDFJS.maxImageSize !== -1 && w * h > PDFJS.maxImageSize) { warn('Image exceeded maximum allowed size and was removed.'); return; } - var imageMask = dict.get('ImageMask', 'IM') || false; + var imageMask = (dict.get('ImageMask', 'IM') || false); + var imgData, args; if (imageMask) { - // This depends on a tmpCanvas beeing filled with the + // This depends on a tmpCanvas being filled with the // current fillStyle, such that processing the pixel // data can't be done here. Instead of creating a // complete PDFImage, only read the information needed @@ -15195,12 +15679,12 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { var imgArray = image.getBytes(bitStrideLength * height); var decode = dict.get('Decode', 'D'); var canTransfer = image instanceof DecodeStream; - var inverseDecode = !!decode && decode[0] > 0; + var inverseDecode = (!!decode && decode[0] > 0); - var imgData = PDFImage.createMask(imgArray, width, height, - canTransfer, inverseDecode); + imgData = PDFImage.createMask(imgArray, width, height, + canTransfer, inverseDecode); imgData.cached = true; - var args = [imgData]; + args = [imgData]; operatorList.addOp(OPS.paintImageMaskXObject, args); if (cacheKey) { cache.key = cacheKey; @@ -15210,45 +15694,48 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { return; } - var softMask = dict.get('SMask', 'SM') || false; - var mask = dict.get('Mask') || false; + var softMask = (dict.get('SMask', 'SM') || false); + var mask = (dict.get('Mask') || false); var SMALL_IMAGE_DIMENSIONS = 200; // Inlining small images into the queue as RGB data - if (inline && !softMask && !mask && - !(image instanceof JpegStream) && + if (inline && !softMask && !mask && !(image instanceof JpegStream) && (w + h) < SMALL_IMAGE_DIMENSIONS) { var imageObj = new PDFImage(this.xref, resources, image, inline, null, null); // We force the use of RGBA_32BPP images here, because we can't handle // any other kind. - var imgData = imageObj.createImageData(/* forceRGBA = */ true); + imgData = imageObj.createImageData(/* forceRGBA = */ true); operatorList.addOp(OPS.paintInlineImageXObject, [imgData]); return; } // If there is no imageMask, create the PDFImage and a lot // of image processing can be done here. - var uniquePrefix = this.uniquePrefix || ''; + var uniquePrefix = (this.uniquePrefix || ''); var objId = 'img_' + uniquePrefix + (++this.idCounters.obj); operatorList.addDependency(objId); - var args = [objId, w, h]; + args = [objId, w, h]; if (!softMask && !mask && image instanceof JpegStream && image.isNativelySupported(this.xref, resources)) { // These JPEGs don't need any more processing so we can just send it. operatorList.addOp(OPS.paintJpegXObject, args); - this.handler.send( - 'obj', [objId, this.pageIndex, 'JpegStream', image.getIR()]); + this.handler.send('obj', + [objId, this.pageIndex, 'JpegStream', image.getIR()]); return; } - - PDFImage.buildImage(function(imageObj) { + PDFImage.buildImage(self.handler, self.xref, resources, image, inline). + then(function(imageObj) { var imgData = imageObj.createImageData(/* forceRGBA = */ false); self.handler.send('obj', [objId, self.pageIndex, 'Image', imgData], - null, [imgData.data.buffer]); - }, self.handler, self.xref, resources, image, inline); + [imgData.data.buffer]); + }).then(null, function (reason) { + warn('Unable to decode image: ' + reason); + self.handler.send('obj', [objId, self.pageIndex, 'Image', null]); + }); + operatorList.addOp(OPS.paintImageXObject, args); if (cacheKey) { cache.key = cacheKey; @@ -15258,71 +15745,70 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { }, handleSMask: function PartialEvaluator_handleSmask(smask, resources, - operatorList) { + operatorList, + stateManager) { var smaskContent = smask.get('G'); var smaskOptions = { subtype: smask.get('S').name, backdrop: smask.get('BC') }; - - this.buildFormXObject(resources, smaskContent, smaskOptions, - operatorList); + return this.buildFormXObject(resources, smaskContent, smaskOptions, + operatorList, stateManager.state.clone()); }, - handleTilingType: function PartialEvaluator_handleTilingType( - fn, args, resources, pattern, patternDict, - operatorList) { + handleTilingType: + function PartialEvaluator_handleTilingType(fn, args, resources, + pattern, patternDict, + operatorList) { // Create an IR of the pattern code. - var tilingOpList = this.getOperatorList(pattern, - patternDict.get('Resources') || resources); - // Add the dependencies to the parent operator list so they are resolved - // before sub operator list is executed synchronously. - operatorList.addDependencies(tilingOpList.dependencies); - operatorList.addOp(fn, getTilingPatternIR({ - fnArray: tilingOpList.fnArray, - argsArray: tilingOpList.argsArray - }, patternDict, args)); + var tilingOpList = new OperatorList(); + return this.getOperatorList(pattern, + (patternDict.get('Resources') || resources), tilingOpList). + then(function () { + // Add the dependencies to the parent operator list so they are + // resolved before sub operator list is executed synchronously. + operatorList.addDependencies(tilingOpList.dependencies); + operatorList.addOp(fn, getTilingPatternIR({ + fnArray: tilingOpList.fnArray, + argsArray: tilingOpList.argsArray + }, patternDict, args)); + }); }, - handleSetFont: function PartialEvaluator_handleSetFont( - resources, fontArgs, fontRef, operatorList) { - + handleSetFont: + function PartialEvaluator_handleSetFont(resources, fontArgs, fontRef, + operatorList, state) { // TODO(mack): Not needed? var fontName; if (fontArgs) { fontArgs = fontArgs.slice(); fontName = fontArgs[0].name; } - var self = this; - var font = this.loadFont(fontName, fontRef, this.xref, resources, - operatorList); - this.state.font = font; - var loadedName = font.loadedName; - if (!font.sent) { - var fontData = font.translated.exportData(); - - self.handler.send('commonobj', [ - loadedName, - 'Font', - fontData - ]); - font.sent = true; - } - return loadedName; + var self = this; + return this.loadFont(fontName, fontRef, this.xref, resources).then( + function (translated) { + if (!translated.font.isType3Font) { + return translated; + } + return translated.loadType3Data(self, resources, operatorList).then( + function () { + return translated; + }); + }).then(function (translated) { + state.font = translated.font; + translated.send(self.handler); + return translated.loadedName; + }); }, - handleText: function PartialEvaluator_handleText(chars) { - var font = this.state.font.translated; + handleText: function PartialEvaluator_handleText(chars, state) { + var font = state.font; var glyphs = font.charsToGlyphs(chars); - var isAddToPathSet = !!(this.state.textRenderingMode & + var isAddToPathSet = !!(state.textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG); if (font.data && (isAddToPathSet || PDFJS.disableFontFace)) { - for (var i = 0; i < glyphs.length; i++) { - if (glyphs[i] === null) { - continue; - } - var fontChar = glyphs[i].fontChar; + var buildPath = function (fontChar) { if (!font.renderer.hasBuiltPath(fontChar)) { var path = font.renderer.getPathJs(fontChar); this.handler.send('commonobj', [ @@ -15331,6 +15817,21 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { path ]); } + }.bind(this); + + for (var i = 0, ii = glyphs.length; i < ii; i++) { + var glyph = glyphs[i]; + if (glyph === null) { + continue; + } + buildPath(glyph.fontChar); + + // If the glyph has an accent we need to build a path for its + // fontChar too, otherwise CanvasGraphics_paintChar will fail. + var accent = glyph.accent; + if (accent && accent.fontChar) { + buildPath(accent.fontChar); + } } } @@ -15338,9 +15839,9 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { }, setGState: function PartialEvaluator_setGState(resources, gState, - operatorList, xref) { + operatorList, xref, + stateManager) { - var self = this; // TODO(mack): This should be rewritten so that this function returns // what should be added to the queue during each iteration function setGStateForKey(gStateObj, key, value) { @@ -15359,10 +15860,14 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { gStateObj.push([key, value]); break; case 'Font': - var loadedName = self.handleSetFont(resources, null, value[0], - operatorList); - operatorList.addDependency(loadedName); - gStateObj.push([key, [loadedName, value[1]]]); + promise = promise.then(function () { + return self.handleSetFont(resources, null, value[0], + operatorList, stateManager.state). + then(function (loadedName) { + operatorList.addDependency(loadedName); + gStateObj.push([key, [loadedName, value[1]]]); + }); + }); break; case 'BM': gStateObj.push([key, value]); @@ -15374,7 +15879,10 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { } var dict = xref.fetchIfRef(value); if (isDict(dict)) { - self.handleSMask(dict, resources, operatorList); + promise = promise.then(function () { + return self.handleSMask(dict, resources, operatorList, + stateManager); + }); gStateObj.push([key, true]); } else { warn('Unsupported SMask type'); @@ -15409,25 +15917,24 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { // This array holds the converted/processed state data. var gStateObj = []; var gStateMap = gState.map; + var self = this; + var promise = Promise.resolve(); for (var key in gStateMap) { var value = gStateMap[key]; setGStateForKey(gStateObj, key, value); } - - operatorList.addOp(OPS.setGState, [gStateObj]); + return promise.then(function () { + operatorList.addOp(OPS.setGState, [gStateObj]); + }); }, loadFont: function PartialEvaluator_loadFont(fontName, font, xref, - resources, - parentOperatorList) { + resources) { function errorFont() { - return { - translated: new ErrorFont('Font ' + fontName + ' is not available'), - loadedName: 'g_font_error' - }; + return Promise.resolve(new TranslatedFont('g_font_error', + new ErrorFont('Font ' + fontName + ' is not available'), font)); } - var fontRef; if (font) { // Loading by ref. assert(isRef(font)); @@ -15449,115 +15956,151 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { if (!isDict(font)) { return errorFont(); } - // Workaround for bad PDF generators that doesn't reference fonts + + // We are holding font.translated references just for fontRef that are not + // dictionaries (Dict). See explanation below. + if (font.translated) { + return font.translated; + } + + var fontCapability = createPromiseCapability(); + + var preEvaluatedFont = this.preEvaluateFont(font, xref); + var descriptor = preEvaluatedFont.descriptor; + var fontID = fontRef.num + '_' + fontRef.gen; + if (isDict(descriptor)) { + if (!descriptor.fontAliases) { + descriptor.fontAliases = Object.create(null); + } + + var fontAliases = descriptor.fontAliases; + var hash = preEvaluatedFont.hash; + if (fontAliases[hash]) { + var aliasFontRef = fontAliases[hash].aliasRef; + if (aliasFontRef && this.fontCache.has(aliasFontRef)) { + this.fontCache.putAlias(fontRef, aliasFontRef); + return this.fontCache.get(fontRef); + } + } + + if (!fontAliases[hash]) { + fontAliases[hash] = { + fontID: Font.getFontID() + }; + } + + fontAliases[hash].aliasRef = fontRef; + fontID = fontAliases[hash].fontID; + } + + // Workaround for bad PDF generators that don't reference fonts // properly, i.e. by not using an object identifier. // Check if the fontRef is a Dict (as opposed to a standard object), // in which case we don't cache the font and instead reference it by // fontName in font.loadedName below. var fontRefIsDict = isDict(fontRef); if (!fontRefIsDict) { - this.fontCache.put(fontRef, font); + this.fontCache.put(fontRef, fontCapability.promise); } - // keep track of each font we translated so the caller can - // load them asynchronously before calling display on a page + // Keep track of each font we translated so the caller can + // load them asynchronously before calling display on a page. font.loadedName = 'g_font_' + (fontRefIsDict ? - fontName.replace(/\W/g, '') : (fontRef.num + '_' + fontRef.gen)); + fontName.replace(/\W/g, '') : fontID); - if (!font.translated) { - var translated; - try { - translated = this.translateFont(font, xref); - } catch (e) { - UnsupportedManager.notify(UNSUPPORTED_FEATURES.font); - translated = new ErrorFont(e instanceof Error ? e.message : e); - } - font.translated = translated; + font.translated = fontCapability.promise; + + // TODO move promises into translate font + var translatedPromise; + try { + translatedPromise = Promise.resolve( + this.translateFont(preEvaluatedFont, xref)); + } catch (e) { + translatedPromise = Promise.reject(e); } - if (font.translated.loadCharProcs) { - var charProcs = font.get('CharProcs').getAll(); - var fontResources = font.get('Resources') || resources; - var charProcKeys = Object.keys(charProcs); - var charProcOperatorList = {}; - for (var i = 0, n = charProcKeys.length; i < n; ++i) { - var key = charProcKeys[i]; - var glyphStream = charProcs[key]; - var operatorList = this.getOperatorList(glyphStream, fontResources); - charProcOperatorList[key] = operatorList.getIR(); - if (!parentOperatorList) { - continue; - } - // Add the dependencies to the parent operator list so they are - // resolved before sub operator list is executed synchronously. - parentOperatorList.addDependencies(charProcOperatorList.dependencies); - } - font.translated.charProcOperatorList = charProcOperatorList; - font.loaded = true; + translatedPromise.then(function (translatedFont) { + fontCapability.resolve(new TranslatedFont(font.loadedName, + translatedFont, font)); + }, function (reason) { + // TODO fontCapability.reject? + UnsupportedManager.notify(UNSUPPORTED_FEATURES.font); + fontCapability.resolve(new TranslatedFont(font.loadedName, + new ErrorFont(reason instanceof Error ? reason.message : reason), + font)); + }); + return fontCapability.promise; + }, + + buildPath: function PartialEvaluator_buildPath(operatorList, fn, args) { + var lastIndex = operatorList.length - 1; + if (lastIndex < 0 || + operatorList.fnArray[lastIndex] !== OPS.constructPath) { + operatorList.addOp(OPS.constructPath, [[fn], args]); } else { - font.loaded = true; + var opArgs = operatorList.argsArray[lastIndex]; + opArgs[0].push(fn); + Array.prototype.push.apply(opArgs[1], args); } - return font; + }, + + handleColorN: function PartialEvaluator_handleColorN(operatorList, fn, args, + cs, patterns, resources, xref) { + // compile tiling patterns + var patternName = args[args.length - 1]; + // SCN/scn applies patterns along with normal colors + var pattern; + if (isName(patternName) && + (pattern = patterns.get(patternName.name))) { + var dict = (isStream(pattern) ? pattern.dict : pattern); + var typeNum = dict.get('PatternType'); + + if (typeNum == TILING_PATTERN) { + var color = cs.base ? cs.base.getRgb(args, 0) : null; + return this.handleTilingType(fn, color, resources, pattern, + dict, operatorList); + } else if (typeNum == SHADING_PATTERN) { + var shading = dict.get('Shading'); + var matrix = dict.get('Matrix'); + pattern = Pattern.parseShading(shading, matrix, xref, resources); + operatorList.addOp(fn, pattern.getIR()); + return Promise.resolve(); + } else { + return Promise.reject('Unknown PatternType: ' + typeNum); + } + } + // TODO shall we fail here? + operatorList.addOp(fn, args); + return Promise.resolve(); }, getOperatorList: function PartialEvaluator_getOperatorList(stream, resources, operatorList, - evaluatorState) { + initialState) { var self = this; var xref = this.xref; - var handler = this.handler; var imageCache = {}; - operatorList = operatorList || new OperatorList(); + assert(operatorList); - resources = resources || new Dict(); - var xobjs = resources.get('XObject') || new Dict(); - var patterns = resources.get('Pattern') || new Dict(); - var preprocessor = new EvaluatorPreprocessor(stream, xref); - if (evaluatorState) { - preprocessor.setState(evaluatorState); - } + resources = (resources || Dict.empty); + var xobjs = (resources.get('XObject') || Dict.empty); + var patterns = (resources.get('Pattern') || Dict.empty); + var stateManager = new StateManager(initialState || new EvalState()); + var preprocessor = new EvaluatorPreprocessor(stream, xref, stateManager); + var timeSlotManager = new TimeSlotManager(); - var promise = new LegacyPromise(); - var operation; - while ((operation = preprocessor.read())) { + return new Promise(function next(resolve, reject) { + timeSlotManager.reset(); + var stop, operation, i, ii, cs; + while (!(stop = timeSlotManager.check()) && + (operation = preprocessor.read())) { var args = operation.args; var fn = operation.fn; - switch (fn) { - case OPS.setStrokeColorN: - case OPS.setFillColorN: - if (args[args.length - 1].code) { - break; - } - // compile tiling patterns - var patternName = args[args.length - 1]; - // SCN/scn applies patterns along with normal colors - var pattern; - if (isName(patternName) && - (pattern = patterns.get(patternName.name))) { - - var dict = isStream(pattern) ? pattern.dict : pattern; - var typeNum = dict.get('PatternType'); - - if (typeNum == TILING_PATTERN) { - self.handleTilingType(fn, args, resources, pattern, dict, - operatorList); - args = []; - continue; - } else if (typeNum == SHADING_PATTERN) { - var shading = dict.get('Shading'); - var matrix = dict.get('Matrix'); - var pattern = Pattern.parseShading(shading, matrix, xref, - resources); - args = pattern.getIR(); - } else { - error('Unkown PatternType ' + typeNum); - } - } - break; + switch (fn | 0) { case OPS.paintXObject: if (args[0].code) { break; @@ -15572,37 +16115,46 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { var xobj = xobjs.get(name); if (xobj) { - assertWellFormed( - isStream(xobj), 'XObject should be a stream'); + assert(isStream(xobj), 'XObject should be a stream'); var type = xobj.dict.get('Subtype'); - assertWellFormed( - isName(type), - 'XObject should have a Name subtype' - ); + assert(isName(type), + 'XObject should have a Name subtype'); - if ('Form' == type.name) { - self.buildFormXObject(resources, xobj, null, operatorList, - preprocessor.getState()); - args = []; - continue; - } else if ('Image' == type.name) { + if (type.name === 'Form') { + stateManager.save(); + return self.buildFormXObject(resources, xobj, null, + operatorList, + stateManager.state.clone()). + then(function () { + stateManager.restore(); + next(resolve, reject); + }, reject); + } else if (type.name === 'Image') { self.buildPaintImageXObject(resources, xobj, false, - operatorList, name, imageCache); + operatorList, name, imageCache); args = []; continue; + } else if (type.name === 'PS') { + // PostScript XObjects are unused when viewing documents. + // See section 4.7.1 of Adobe's PDF reference. + info('Ignored XObject subtype PS'); + continue; } else { error('Unhandled XObject subtype ' + type.name); } } break; case OPS.setFont: + var fontSize = args[1]; // eagerly collect all fonts - var loadedName = self.handleSetFont(resources, args, null, - operatorList); - operatorList.addDependency(loadedName); - args[0] = loadedName; - break; + return self.handleSetFont(resources, args, null, + operatorList, stateManager.state). + then(function (loadedName) { + operatorList.addDependency(loadedName); + operatorList.addOp(OPS.setFont, [loadedName, fontSize]); + next(resolve, reject); + }, reject); case OPS.endInlineImage: var cacheKey = args[0].cacheKey; if (cacheKey && imageCache.key === cacheKey) { @@ -15611,57 +16163,126 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { continue; } self.buildPaintImageXObject(resources, args[0], true, - operatorList, cacheKey, imageCache); + operatorList, cacheKey, imageCache); args = []; continue; - case OPS.save: - var old = this.state; - this.stateStack.push(this.state); - this.state = old.clone(); - break; - case OPS.restore: - var prev = this.stateStack.pop(); - if (prev) { - this.state = prev; - } - break; case OPS.showText: - args[0] = this.handleText(args[0]); + args[0] = self.handleText(args[0], stateManager.state); break; case OPS.showSpacedText: var arr = args[0]; + var combinedGlyphs = []; var arrLength = arr.length; - for (var i = 0; i < arrLength; ++i) { - if (isString(arr[i])) { - arr[i] = this.handleText(arr[i]); + for (i = 0; i < arrLength; ++i) { + var arrItem = arr[i]; + if (isString(arrItem)) { + Array.prototype.push.apply(combinedGlyphs, + self.handleText(arrItem, stateManager.state)); + } else if (isNum(arrItem)) { + combinedGlyphs.push(arrItem); } } + args[0] = combinedGlyphs; + fn = OPS.showText; break; case OPS.nextLineShowText: - args[0] = this.handleText(args[0]); + operatorList.addOp(OPS.nextLine); + args[0] = self.handleText(args[0], stateManager.state); + fn = OPS.showText; break; case OPS.nextLineSetSpacingShowText: - args[2] = this.handleText(args[2]); + operatorList.addOp(OPS.nextLine); + operatorList.addOp(OPS.setWordSpacing, [args.shift()]); + operatorList.addOp(OPS.setCharSpacing, [args.shift()]); + args[0] = self.handleText(args[0], stateManager.state); + fn = OPS.showText; break; case OPS.setTextRenderingMode: - this.state.textRenderingMode = args[0]; + stateManager.state.textRenderingMode = args[0]; break; - // Parse the ColorSpace data to a raw format. + case OPS.setFillColorSpace: + stateManager.state.fillColorSpace = + ColorSpace.parse(args[0], xref, resources); + continue; case OPS.setStrokeColorSpace: - args = [ColorSpace.parseToIR(args[0], xref, resources)]; + stateManager.state.strokeColorSpace = + ColorSpace.parse(args[0], xref, resources); + continue; + case OPS.setFillColor: + cs = stateManager.state.fillColorSpace; + args = cs.getRgb(args, 0); + fn = OPS.setFillRGBColor; + break; + case OPS.setStrokeColor: + cs = stateManager.state.strokeColorSpace; + args = cs.getRgb(args, 0); + fn = OPS.setStrokeRGBColor; + break; + case OPS.setFillGray: + stateManager.state.fillColorSpace = ColorSpace.singletons.gray; + args = ColorSpace.singletons.gray.getRgb(args, 0); + fn = OPS.setFillRGBColor; + break; + case OPS.setStrokeGray: + stateManager.state.strokeColorSpace = ColorSpace.singletons.gray; + args = ColorSpace.singletons.gray.getRgb(args, 0); + fn = OPS.setStrokeRGBColor; + break; + case OPS.setFillCMYKColor: + stateManager.state.fillColorSpace = ColorSpace.singletons.cmyk; + args = ColorSpace.singletons.cmyk.getRgb(args, 0); + fn = OPS.setFillRGBColor; + break; + case OPS.setStrokeCMYKColor: + stateManager.state.strokeColorSpace = ColorSpace.singletons.cmyk; + args = ColorSpace.singletons.cmyk.getRgb(args, 0); + fn = OPS.setStrokeRGBColor; + break; + case OPS.setFillRGBColor: + stateManager.state.fillColorSpace = ColorSpace.singletons.rgb; + args = ColorSpace.singletons.rgb.getRgb(args, 0); + break; + case OPS.setStrokeRGBColor: + stateManager.state.strokeColorSpace = ColorSpace.singletons.rgb; + args = ColorSpace.singletons.rgb.getRgb(args, 0); + break; + case OPS.setFillColorN: + cs = stateManager.state.fillColorSpace; + if (cs.name === 'Pattern') { + return self.handleColorN(operatorList, OPS.setFillColorN, + args, cs, patterns, resources, xref).then(function() { + next(resolve, reject); + }, reject); + } + args = cs.getRgb(args, 0); + fn = OPS.setFillRGBColor; + break; + case OPS.setStrokeColorN: + cs = stateManager.state.strokeColorSpace; + if (cs.name === 'Pattern') { + return self.handleColorN(operatorList, OPS.setStrokeColorN, + args, cs, patterns, resources, xref).then(function() { + next(resolve, reject); + }, reject); + } + args = cs.getRgb(args, 0); + fn = OPS.setStrokeRGBColor; break; + case OPS.shadingFill: var shadingRes = resources.get('Shading'); - if (!shadingRes) + if (!shadingRes) { error('No shading resource found'); + } var shading = shadingRes.get(args[0].name); - if (!shading) + if (!shading) { error('No shading object found'); + } - var shadingFill = Pattern.parseShading( - shading, null, xref, resources); + var shadingFill = Pattern.parseShading(shading, null, xref, + resources); var patternIR = shadingFill.getIR(); args = [patternIR]; fn = OPS.shadingFill; @@ -15670,64 +16291,212 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { var dictName = args[0]; var extGState = resources.get('ExtGState'); - if (!isDict(extGState) || !extGState.has(dictName.name)) + if (!isDict(extGState) || !extGState.has(dictName.name)) { break; + } var gState = extGState.get(dictName.name); - self.setGState(resources, gState, operatorList, xref); - args = []; + return self.setGState(resources, gState, operatorList, xref, + stateManager).then(function() { + next(resolve, reject); + }, reject); + case OPS.moveTo: + case OPS.lineTo: + case OPS.curveTo: + case OPS.curveTo2: + case OPS.curveTo3: + case OPS.closePath: + self.buildPath(operatorList, fn, args); continue; - } // switch - + } operatorList.addOp(fn, args); - } - - // some pdf don't close all restores inside object/form - // closing those for them - for (var i = 0, ii = preprocessor.savedStatesDepth; i < ii; i++) { - operatorList.addOp(OPS.restore, []); - } - - return operatorList; + } + if (stop) { + deferred.then(function () { + next(resolve, reject); + }); + return; + } + // Some PDFs don't close all restores inside object/form. + // Closing those for them. + for (i = 0, ii = preprocessor.savedStatesDepth; i < ii; i++) { + operatorList.addOp(OPS.restore, []); + } + resolve(); + }); }, - getTextContent: function PartialEvaluator_getTextContent( - stream, resources, textState) { + getTextContent: function PartialEvaluator_getTextContent(stream, resources, + stateManager) { - textState = textState || new TextState(); + stateManager = (stateManager || new StateManager(new TextState())); - var bidiTexts = []; + var textContent = { + items: [], + styles: Object.create(null) + }; + var bidiTexts = textContent.items; var SPACE_FACTOR = 0.35; var MULTI_SPACE_FACTOR = 1.5; var self = this; var xref = this.xref; - function handleSetFont(fontName, fontRef) { - return self.loadFont(fontName, fontRef, xref, resources, null); - } + resources = (xref.fetchIfRef(resources) || Dict.empty); - resources = xref.fetchIfRef(resources) || new Dict(); // The xobj is parsed iff it's needed, e.g. if there is a `DO` cmd. var xobjs = null; var xobjsCache = {}; - var preprocessor = new EvaluatorPreprocessor(stream, xref); - var res = resources; + var preprocessor = new EvaluatorPreprocessor(stream, xref, stateManager); - var chunkBuf = []; - var font = null; - var charSpace = 0, wordSpace = 0; var operation; - while ((operation = preprocessor.read())) { + var textState; + + function newTextChunk() { + var font = textState.font; + if (!(font.loadedName in textContent.styles)) { + textContent.styles[font.loadedName] = { + fontFamily: font.fallbackName, + ascent: font.ascent, + descent: font.descent, + vertical: font.vertical + }; + } + return { + str: '', + dir: null, + width: 0, + height: 0, + transform: null, + fontName: font.loadedName + }; + } + + function runBidi(textChunk) { + var bidiResult = PDFJS.bidi(textChunk.str, -1, textState.font.vertical); + textChunk.str = bidiResult.str; + textChunk.dir = bidiResult.dir; + return textChunk; + } + + function handleSetFont(fontName, fontRef) { + return self.loadFont(fontName, fontRef, xref, resources). + then(function (translated) { + textState.font = translated.font; + textState.fontMatrix = translated.font.fontMatrix || + FONT_IDENTITY_MATRIX; + }); + } + + function buildTextGeometry(chars, textChunk) { + var font = textState.font; + textChunk = textChunk || newTextChunk(); + if (!textChunk.transform) { + // 9.4.4 Text Space Details + var tsm = [textState.fontSize * textState.textHScale, 0, + 0, textState.fontSize, + 0, textState.textRise]; + var trm = textChunk.transform = Util.transform(textState.ctm, + Util.transform(textState.textMatrix, tsm)); + if (!font.vertical) { + textChunk.height = Math.sqrt(trm[2] * trm[2] + trm[3] * trm[3]); + } else { + textChunk.width = Math.sqrt(trm[0] * trm[0] + trm[1] * trm[1]); + } + } + var width = 0; + var height = 0; + var glyphs = font.charsToGlyphs(chars); + var defaultVMetrics = font.defaultVMetrics; + for (var i = 0; i < glyphs.length; i++) { + var glyph = glyphs[i]; + if (!glyph) { // Previous glyph was a space. + width += textState.wordSpacing * textState.textHScale; + continue; + } + var vMetricX = null; + var vMetricY = null; + var glyphWidth = null; + if (font.vertical) { + if (glyph.vmetric) { + glyphWidth = glyph.vmetric[0]; + vMetricX = glyph.vmetric[1]; + vMetricY = glyph.vmetric[2]; + } else { + glyphWidth = glyph.width; + vMetricX = glyph.width * 0.5; + vMetricY = defaultVMetrics[2]; + } + } else { + glyphWidth = glyph.width; + } + + var glyphUnicode = glyph.unicode; + if (glyphUnicode in NormalizedUnicodes) { + glyphUnicode = NormalizedUnicodes[glyphUnicode]; + } + glyphUnicode = reverseIfRtl(glyphUnicode); + + // The following will calculate the x and y of the individual glyphs. + // if (font.vertical) { + // tsm[4] -= vMetricX * Math.abs(textState.fontSize) * + // textState.fontMatrix[0]; + // tsm[5] -= vMetricY * textState.fontSize * + // textState.fontMatrix[0]; + // } + // var trm = Util.transform(textState.textMatrix, tsm); + // var pt = Util.applyTransform([trm[4], trm[5]], textState.ctm); + // var x = pt[0]; + // var y = pt[1]; + + var tx = 0; + var ty = 0; + if (!font.vertical) { + var w0 = glyphWidth * textState.fontMatrix[0]; + tx = (w0 * textState.fontSize + textState.charSpacing) * + textState.textHScale; + width += tx; + } else { + var w1 = glyphWidth * textState.fontMatrix[0]; + ty = w1 * textState.fontSize + textState.charSpacing; + height += ty; + } + textState.translateTextMatrix(tx, ty); + + textChunk.str += glyphUnicode; + } + + var a = textState.textLineMatrix[0]; + var b = textState.textLineMatrix[1]; + var scaleLineX = Math.sqrt(a * a + b * b); + a = textState.ctm[0]; + b = textState.ctm[1]; + var scaleCtmX = Math.sqrt(a * a + b * b); + if (!font.vertical) { + textChunk.width += width * scaleCtmX * scaleLineX; + } else { + textChunk.height += Math.abs(height * scaleCtmX * scaleLineX); + } + return textChunk; + } + + var timeSlotManager = new TimeSlotManager(); + + return new Promise(function next(resolve, reject) { + timeSlotManager.reset(); + var stop; + while (!(stop = timeSlotManager.check()) && + (operation = preprocessor.read())) { + textState = stateManager.state; var fn = operation.fn; var args = operation.args; - switch (fn) { - // TODO: Add support for SAVE/RESTORE and XFORM here. + switch (fn | 0) { case OPS.setFont: - font = handleSetFont(args[0].name).translated; textState.fontSize = args[1]; - break; + return handleSetFont(args[0].name).then(function() { + next(resolve, reject); + }, reject); case OPS.setTextRise: textState.textRise = args[0]; break; @@ -15738,90 +16507,108 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { textState.leading = args[0]; break; case OPS.moveText: - textState.translateTextMatrix(args[0], args[1]); + textState.translateTextLineMatrix(args[0], args[1]); + textState.textMatrix = textState.textLineMatrix.slice(); break; case OPS.setLeadingMoveText: textState.leading = -args[1]; - textState.translateTextMatrix(args[0], args[1]); + textState.translateTextLineMatrix(args[0], args[1]); + textState.textMatrix = textState.textLineMatrix.slice(); break; case OPS.nextLine: - textState.translateTextMatrix(0, -textState.leading); + textState.carriageReturn(); break; case OPS.setTextMatrix: - textState.setTextMatrix(args[0], args[1], - args[2], args[3], args[4], args[5]); + textState.setTextMatrix(args[0], args[1], args[2], args[3], + args[4], args[5]); + textState.setTextLineMatrix(args[0], args[1], args[2], args[3], + args[4], args[5]); break; case OPS.setCharSpacing: - charSpace = args[0]; + textState.charSpacing = args[0]; break; case OPS.setWordSpacing: - wordSpace = args[0]; + textState.wordSpacing = args[0]; break; case OPS.beginText: - textState.initialiseTextObj(); + textState.textMatrix = IDENTITY_MATRIX.slice(); + textState.textLineMatrix = IDENTITY_MATRIX.slice(); break; case OPS.showSpacedText: var items = args[0]; + var textChunk = newTextChunk(); + var offset; for (var j = 0, jj = items.length; j < jj; j++) { if (typeof items[j] === 'string') { - chunkBuf.push(fontCharsToUnicode(items[j], font)); - } else if (items[j] < 0 && font.spaceWidth > 0) { - var fakeSpaces = -items[j] / font.spaceWidth; - if (fakeSpaces > MULTI_SPACE_FACTOR) { - fakeSpaces = Math.round(fakeSpaces); - while (fakeSpaces--) { - chunkBuf.push(' '); + buildTextGeometry(items[j], textChunk); + } else { + var val = items[j] / 1000; + if (!textState.font.vertical) { + offset = -val * textState.fontSize * textState.textHScale * + textState.textMatrix[0]; + textState.translateTextMatrix(offset, 0); + textChunk.width += offset; + } else { + offset = -val * textState.fontSize * + textState.textMatrix[3]; + textState.translateTextMatrix(0, offset); + textChunk.height += offset; + } + if (items[j] < 0 && textState.font.spaceWidth > 0) { + var fakeSpaces = -items[j] / textState.font.spaceWidth; + if (fakeSpaces > MULTI_SPACE_FACTOR) { + fakeSpaces = Math.round(fakeSpaces); + while (fakeSpaces--) { + textChunk.str += ' '; + } + } else if (fakeSpaces > SPACE_FACTOR) { + textChunk.str += ' '; } - } else if (fakeSpaces > SPACE_FACTOR) { - chunkBuf.push(' '); } } } + bidiTexts.push(runBidi(textChunk)); break; case OPS.showText: - chunkBuf.push(fontCharsToUnicode(args[0], font)); + bidiTexts.push(runBidi(buildTextGeometry(args[0]))); break; case OPS.nextLineShowText: - // For search, adding a extra white space for line breaks would be - // better here, but that causes too much spaces in the - // text-selection divs. - chunkBuf.push(fontCharsToUnicode(args[0], font)); + textState.carriageReturn(); + bidiTexts.push(runBidi(buildTextGeometry(args[0]))); break; case OPS.nextLineSetSpacingShowText: - // Note comment in "'" - chunkBuf.push(fontCharsToUnicode(args[2], font)); + textState.wordSpacing = args[0]; + textState.charSpacing = args[1]; + textState.carriageReturn(); + bidiTexts.push(runBidi(buildTextGeometry(args[2]))); break; case OPS.paintXObject: - // Set the chunk such that the following if won't add something - // to the state. - chunkBuf.length = 0; - if (args[0].code) { break; } if (!xobjs) { - xobjs = resources.get('XObject') || new Dict(); + xobjs = (resources.get('XObject') || Dict.empty); } var name = args[0].name; if (xobjsCache.key === name) { if (xobjsCache.texts) { - Util.concatenateToArray(bidiTexts, xobjsCache.texts); + Util.concatenateToArray(bidiTexts, xobjsCache.texts.items); + Util.extendObj(textContent.styles, xobjsCache.texts.styles); } break; } var xobj = xobjs.get(name); - if (!xobj) + if (!xobj) { break; - assertWellFormed(isStream(xobj), 'XObject should be a stream'); + } + assert(isStream(xobj), 'XObject should be a stream'); var type = xobj.dict.get('Subtype'); - assertWellFormed( - isName(type), - 'XObject should have a Name subtype' - ); + assert(isName(type), + 'XObject should have a Name subtype'); if ('Form' !== type.name) { xobjsCache.key = name; @@ -15829,71 +16616,67 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { break; } - var formTexts = this.getTextContent( - xobj, - xobj.dict.get('Resources') || resources, - textState - ); - xobjsCache.key = name; - xobjsCache.texts = formTexts; - Util.concatenateToArray(bidiTexts, formTexts); - break; + stateManager.save(); + var matrix = xobj.dict.get('Matrix'); + if (isArray(matrix) && matrix.length === 6) { + stateManager.transform(matrix); + } + + return self.getTextContent(xobj, + xobj.dict.get('Resources') || resources, stateManager). + then(function (formTextContent) { + Util.concatenateToArray(bidiTexts, formTextContent.items); + Util.extendObj(textContent.styles, formTextContent.styles); + stateManager.restore(); + + xobjsCache.key = name; + xobjsCache.texts = formTextContent; + + next(resolve, reject); + }, reject); case OPS.setGState: var dictName = args[0]; var extGState = resources.get('ExtGState'); - if (!isDict(extGState) || !extGState.has(dictName.name)) + if (!isDict(extGState) || !extGState.has(dictName.name)) { break; + } - var gsState = extGState.get(dictName.name); - - for (var i = 0; i < gsState.length; i++) { - if (gsState[i] === 'Font') { - font = handleSetFont(args[0].name).translated; + var gsStateMap = extGState.get(dictName.name); + var gsStateFont = null; + for (var key in gsStateMap) { + if (key === 'Font') { + assert(!gsStateFont); + gsStateFont = gsStateMap[key]; } } + if (gsStateFont) { + textState.fontSize = gsStateFont[1]; + return handleSetFont(gsStateFont[0]).then(function() { + next(resolve, reject); + }, reject); + } break; } // switch - - if (chunkBuf.length > 0) { - var chunk = chunkBuf.join(''); - var bidiResult = PDFJS.bidi(chunk, -1, font.vertical); - var bidiText = { - str: bidiResult.str, - dir: bidiResult.dir - }; - var renderParams = textState.calcRenderParams(preprocessor.ctm); - var fontHeight = textState.fontSize * renderParams.vScale; - var fontAscent = font.ascent ? font.ascent * fontHeight : - font.descent ? (1 + font.descent) * fontHeight : fontHeight; - bidiText.x = renderParams.renderMatrix[4] - (fontAscent * - Math.sin(renderParams.angle)); - bidiText.y = renderParams.renderMatrix[5] + (fontAscent * - Math.cos(renderParams.angle)); - if (bidiText.dir == 'ttb') { - bidiText.x += renderParams.vScale / 2; - bidiText.y -= renderParams.vScale; - } - bidiText.angle = renderParams.angle; - bidiText.size = fontHeight; - bidiTexts.push(bidiText); - - chunkBuf.length = 0; - } - } // while - - return bidiTexts; + } // while + if (stop) { + deferred.then(function () { + next(resolve, reject); + }); + return; + } + resolve(textContent); + }); }, extractDataStructures: function partialEvaluatorExtractDataStructures(dict, baseDict, xref, properties) { // 9.10.2 - var toUnicode = dict.get('ToUnicode') || - baseDict.get('ToUnicode'); - if (toUnicode) + var toUnicode = (dict.get('ToUnicode') || baseDict.get('ToUnicode')); + if (toUnicode) { properties.toUnicode = this.readToUnicode(toUnicode, xref, properties); - + } if (properties.composite) { // CIDSystemInfo helps to match CID to glyphs var cidSystemInfo = dict.get('CIDSystemInfo'); @@ -15906,8 +16689,9 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { } var cidToGidMap = dict.get('CIDToGIDMap'); - if (isStream(cidToGidMap)) + if (isStream(cidToGidMap)) { properties.cidToGidMap = this.readCidToGidMap(cidToGidMap); + } } // Based on 9.6.6 of the spec the encoding can come from multiple places @@ -15918,22 +16702,24 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { // differences to be merged in here not require us to hold on to it. var differences = []; var baseEncodingName = null; + var encoding; if (dict.has('Encoding')) { - var encoding = dict.get('Encoding'); + encoding = dict.get('Encoding'); if (isDict(encoding)) { baseEncodingName = encoding.get('BaseEncoding'); - baseEncodingName = isName(baseEncodingName) ? baseEncodingName.name : - null; + baseEncodingName = (isName(baseEncodingName) ? + baseEncodingName.name : null); // Load the differences between the base and original if (encoding.has('Differences')) { var diffEncoding = encoding.get('Differences'); var index = 0; for (var j = 0, jj = diffEncoding.length; j < jj; j++) { var data = diffEncoding[j]; - if (isNum(data)) + if (isNum(data)) { index = data; - else + } else { differences[index++] = data.name; + } } } } else if (isName(encoding)) { @@ -15953,14 +16739,14 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { if (baseEncodingName) { properties.defaultEncoding = Encodings[baseEncodingName].slice(); } else { - var encoding = properties.type === 'TrueType' ? - Encodings.WinAnsiEncoding : - Encodings.StandardEncoding; + encoding = (properties.type === 'TrueType' ? + Encodings.WinAnsiEncoding : Encodings.StandardEncoding); // The Symbolic attribute can be misused for regular fonts // Heuristic: we have to check if the font is a standard one also if (!!(properties.flags & FontFlags.Symbolic)) { - encoding = !properties.file && /Symbol/i.test(properties.name) ? - Encodings.SymbolSetEncoding : Encodings.MacRomanEncoding; + encoding = (!properties.file && /Symbol/i.test(properties.name) ? + Encodings.SymbolSetEncoding : + Encodings.MacRomanEncoding); } properties.defaultEncoding = encoding; } @@ -15972,14 +16758,13 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { readToUnicode: function PartialEvaluator_readToUnicode(toUnicode) { var cmapObj = toUnicode; - var charToUnicode = []; if (isName(cmapObj)) { return CMapFactory.create(cmapObj).map; } else if (isStream(cmapObj)) { var cmap = CMapFactory.create(cmapObj).map; // Convert UTF-16BE // NOTE: cmap can be a sparse array, so use forEach instead of for(;;) - // to iterate over all keys. + // to iterate over all keys. cmap.forEach(function(token, i) { var str = []; for (var k = 0; k < token.length; k += 2) { @@ -15998,6 +16783,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { } return null; }, + readCidToGidMap: function PartialEvaluator_readCidToGidMap(cidToGidStream) { // Extract the encoding from the CIDToGIDMap var glyphsData = cidToGidStream.getBytes(); @@ -16006,69 +16792,74 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { var result = []; for (var j = 0, jj = glyphsData.length; j < jj; j++) { var glyphID = (glyphsData[j++] << 8) | glyphsData[j]; - if (glyphID === 0) + if (glyphID === 0) { continue; - + } var code = j >> 1; result[code] = glyphID; } return result; }, - extractWidths: function PartialEvaluator_extractWidths(dict, - xref, - descriptor, - properties) { + extractWidths: function PartialEvaluator_extractWidths(dict, xref, + descriptor, + properties) { var glyphsWidths = []; var defaultWidth = 0; var glyphsVMetrics = []; var defaultVMetrics; + var i, ii, j, jj, start, code, widths; if (properties.composite) { defaultWidth = dict.get('DW') || 1000; - var widths = dict.get('W'); + widths = dict.get('W'); if (widths) { - for (var i = 0, ii = widths.length; i < ii; i++) { - var start = widths[i++]; - var code = xref.fetchIfRef(widths[i]); + for (i = 0, ii = widths.length; i < ii; i++) { + start = widths[i++]; + code = xref.fetchIfRef(widths[i]); if (isArray(code)) { - for (var j = 0, jj = code.length; j < jj; j++) + for (j = 0, jj = code.length; j < jj; j++) { glyphsWidths[start++] = code[j]; + } } else { var width = widths[++i]; - for (var j = start; j <= code; j++) + for (j = start; j <= code; j++) { glyphsWidths[j] = width; + } } } } if (properties.vertical) { - var vmetrics = dict.get('DW2') || [880, -1000]; + var vmetrics = (dict.get('DW2') || [880, -1000]); defaultVMetrics = [vmetrics[1], defaultWidth * 0.5, vmetrics[0]]; vmetrics = dict.get('W2'); if (vmetrics) { - for (var i = 0, ii = vmetrics.length; i < ii; i++) { - var start = vmetrics[i++]; - var code = xref.fetchIfRef(vmetrics[i]); + for (i = 0, ii = vmetrics.length; i < ii; i++) { + start = vmetrics[i++]; + code = xref.fetchIfRef(vmetrics[i]); if (isArray(code)) { - for (var j = 0, jj = code.length; j < jj; j++) + for (j = 0, jj = code.length; j < jj; j++) { glyphsVMetrics[start++] = [code[j++], code[j++], code[j]]; + } } else { var vmetric = [vmetrics[++i], vmetrics[++i], vmetrics[++i]]; - for (var j = start; j <= code; j++) + for (j = start; j <= code; j++) { glyphsVMetrics[j] = vmetric; + } } } } } } else { var firstChar = properties.firstChar; - var widths = dict.get('Widths'); + widths = dict.get('Widths'); if (widths) { - var j = firstChar; - for (var i = 0, ii = widths.length; i < ii; i++) + j = firstChar; + for (i = 0, ii = widths.length; i < ii; i++) { glyphsWidths[j++] = widths[i]; - defaultWidth = parseFloat(descriptor.get('MissingWidth')) || 0; + } + defaultWidth = (parseFloat(descriptor.get('MissingWidth')) || 0); } else { // Trying get the BaseFont metrics (see comment above). var baseFontName = dict.get('BaseFont'); @@ -16083,11 +16874,13 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { } // Heuristic: detection of monospace font by checking all non-zero widths - var isMonospace = true, firstWidth = defaultWidth; + var isMonospace = true; + var firstWidth = defaultWidth; for (var glyph in glyphsWidths) { var glyphWidth = glyphsWidths[glyph]; - if (!glyphWidth) + if (!glyphWidth) { continue; + } if (!firstWidth) { firstWidth = glyphWidth; continue; @@ -16097,8 +16890,9 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { break; } } - if (isMonospace) + if (isMonospace) { properties.flags |= FontFlags.FixedPitch; + } properties.defaultWidth = defaultWidth; properties.widths = glyphsWidths; @@ -16107,17 +16901,17 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { }, isSerifFont: function PartialEvaluator_isSerifFont(baseFontName) { - // Simulating descriptor flags attribute var fontNameWoStyle = baseFontName.split('-')[0]; return (fontNameWoStyle in serifFonts) || - (fontNameWoStyle.search(/serif/gi) !== -1); + (fontNameWoStyle.search(/serif/gi) !== -1); }, getBaseFontMetrics: function PartialEvaluator_getBaseFontMetrics(name) { - var defaultWidth = 0, widths = [], monospace = false; - - var lookupName = stdFontMap[name] || name; + var defaultWidth = 0; + var widths = []; + var monospace = false; + var lookupName = (stdFontMap[name] || name); if (!(lookupName in Metrics)) { // Use default fonts for looking up font metrics if the passed @@ -16144,8 +16938,9 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { }; }, - buildCharCodeToWidth: function PartialEvaluator_bulildCharCodeToWidth( - widthsByGlyphName, properties) { + buildCharCodeToWidth: + function PartialEvaluator_bulildCharCodeToWidth(widthsByGlyphName, + properties) { var widths = Object.create(null); var differences = properties.differences; var encoding = properties.defaultEncoding; @@ -16163,44 +16958,92 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { return widths; }, - translateFont: function PartialEvaluator_translateFont(dict, - xref) { + preEvaluateFont: function PartialEvaluator_preEvaluateFont(dict, xref) { var baseDict = dict; var type = dict.get('Subtype'); - assertWellFormed(isName(type), 'invalid font Subtype'); + assert(isName(type), 'invalid font Subtype'); var composite = false; + var uint8array; if (type.name == 'Type0') { // If font is a composite // - get the descendant font // - set the type according to the descendant font // - get the FontDescriptor from the descendant font var df = dict.get('DescendantFonts'); - if (!df) + if (!df) { error('Descendant fonts are not specified'); - - dict = isArray(df) ? xref.fetchIfRef(df[0]) : df; + } + dict = (isArray(df) ? xref.fetchIfRef(df[0]) : df); type = dict.get('Subtype'); - assertWellFormed(isName(type), 'invalid font Subtype'); + assert(isName(type), 'invalid font Subtype'); composite = true; } - var maxCharIndex = composite ? 0xFFFF : 0xFF; var descriptor = dict.get('FontDescriptor'); + if (descriptor) { + var hash = new MurmurHash3_64(); + var encoding = baseDict.getRaw('Encoding'); + if (isName(encoding)) { + hash.update(encoding.name); + } else if (isRef(encoding)) { + hash.update(encoding.num + '_' + encoding.gen); + } + + var toUnicode = dict.get('ToUnicode') || baseDict.get('ToUnicode'); + if (isStream(toUnicode)) { + var stream = toUnicode.str || toUnicode; + uint8array = stream.buffer ? + new Uint8Array(stream.buffer.buffer, 0, stream.bufferLength) : + new Uint8Array(stream.bytes.buffer, + stream.start, stream.end - stream.start); + hash.update(uint8array); + + } else if (isName(toUnicode)) { + hash.update(toUnicode.name); + } + + var widths = dict.get('Widths') || baseDict.get('Widths'); + if (widths) { + uint8array = new Uint8Array(new Uint32Array(widths).buffer); + hash.update(uint8array); + } + } + + return { + descriptor: descriptor, + dict: dict, + baseDict: baseDict, + composite: composite, + hash: hash ? hash.hexdigest() : '' + }; + }, + + translateFont: function PartialEvaluator_translateFont(preEvaluatedFont, + xref) { + var baseDict = preEvaluatedFont.baseDict; + var dict = preEvaluatedFont.dict; + var composite = preEvaluatedFont.composite; + var descriptor = preEvaluatedFont.descriptor; + var type = dict.get('Subtype'); + var maxCharIndex = (composite ? 0xFFFF : 0xFF); + var properties; + if (!descriptor) { if (type.name == 'Type3') { // FontDescriptor is only required for Type3 fonts when the document // is a tagged pdf. Create a barbebones one to get by. - descriptor = new Dict(); + descriptor = new Dict(null); descriptor.set('FontName', Name.get(type.name)); } else { // Before PDF 1.5 if the font was one of the base 14 fonts, having a // FontDescriptor was not required. // This case is here for compatibility. var baseFontName = dict.get('BaseFont'); - if (!isName(baseFontName)) + if (!isName(baseFontName)) { error('Base font is not specified'); + } // Using base font name as a font name. baseFontName = baseFontName.name.replace(/[,_]/g, '-'); @@ -16208,13 +17051,13 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { // Simulating descriptor flags attribute var fontNameWoStyle = baseFontName.split('-')[0]; - var flags = ( - this.isSerifFont(fontNameWoStyle) ? FontFlags.Serif : 0) | + var flags = + (this.isSerifFont(fontNameWoStyle) ? FontFlags.Serif : 0) | (metrics.monospace ? FontFlags.FixedPitch : 0) | (symbolsFonts[fontNameWoStyle] ? FontFlags.Symbolic : - FontFlags.Nonsymbolic); + FontFlags.Nonsymbolic); - var properties = { + properties = { type: type.name, name: baseFontName, widths: metrics.widths, @@ -16226,7 +17069,6 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { this.extractDataStructures(dict, dict, xref, properties); properties.widths = this.buildCharCodeToWidth(metrics.widths, properties); - return new Font(baseFontName, null, properties); } } @@ -16236,12 +17078,12 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { // to ignore this rule when a variant of a standart font is used. // TODO Fill the width array depending on which of the base font this is // a variant. - var firstChar = dict.get('FirstChar') || 0; - var lastChar = dict.get('LastChar') || maxCharIndex; + var firstChar = (dict.get('FirstChar') || 0); + var lastChar = (dict.get('LastChar') || maxCharIndex); var fontName = descriptor.get('FontName'); var baseFont = dict.get('BaseFont'); - // Some bad pdf's have a string as the font name. + // Some bad PDFs have a string as the font name. if (isString(fontName)) { fontName = Name.get(fontName); } @@ -16256,26 +17098,31 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { info('The FontDescriptor\'s FontName is "' + fontNameStr + '" but should be the same as the Font\'s BaseFont "' + baseFontStr + '"'); + // Workaround for cases where e.g. fontNameStr = 'Arial' and + // baseFontStr = 'Arial,Bold' (needed when no font file is embedded). + if (fontNameStr && baseFontStr && + baseFontStr.search(fontNameStr) === 0) { + fontName = baseFont; + } } } - fontName = fontName || baseFont; + fontName = (fontName || baseFont); - assertWellFormed(isName(fontName), 'invalid font name'); + assert(isName(fontName), 'invalid font name'); var fontFile = descriptor.get('FontFile', 'FontFile2', 'FontFile3'); if (fontFile) { if (fontFile.dict) { var subtype = fontFile.dict.get('Subtype'); - if (subtype) + if (subtype) { subtype = subtype.name; - + } var length1 = fontFile.dict.get('Length1'); - var length2 = fontFile.dict.get('Length2'); } } - var properties = { + properties = { type: type.name, name: fontName.name, subtype: subtype, @@ -16286,9 +17133,9 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { composite: composite, wideChars: composite, fixedPitch: false, - fontMatrix: dict.get('FontMatrix') || FONT_IDENTITY_MATRIX, + fontMatrix: (dict.get('FontMatrix') || FONT_IDENTITY_MATRIX), firstChar: firstChar || 0, - lastChar: lastChar || maxCharIndex, + lastChar: (lastChar || maxCharIndex), bbox: descriptor.get('FontBBox'), ascent: descriptor.get('Ascent'), descent: descriptor.get('Descent'), @@ -16304,14 +17151,15 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { if (isName(cidEncoding)) { properties.cidEncoding = cidEncoding.name; } - properties.cMap = CMapFactory.create(cidEncoding, PDFJS.cMapUrl, null); + properties.cMap = CMapFactory.create(cidEncoding, + { url: PDFJS.cMapUrl, packed: PDFJS.cMapPacked }, null); properties.vertical = properties.cMap.vertical; } this.extractDataStructures(dict, baseDict, xref, properties); this.extractWidths(dict, xref, descriptor, properties); if (type.name === 'Type3') { - properties.coded = true; + properties.isType3Font = true; } return new Font(fontName.name, fontFile, properties); @@ -16321,65 +17169,111 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { return PartialEvaluator; })(); +var TranslatedFont = (function TranslatedFontClosure() { + function TranslatedFont(loadedName, font, dict) { + this.loadedName = loadedName; + this.font = font; + this.dict = dict; + this.type3Loaded = null; + this.sent = false; + } + TranslatedFont.prototype = { + send: function (handler) { + if (this.sent) { + return; + } + var fontData = this.font.exportData(); + handler.send('commonobj', [ + this.loadedName, + 'Font', + fontData + ]); + this.sent = true; + }, + loadType3Data: function (evaluator, resources, parentOperatorList) { + assert(this.font.isType3Font); + + if (this.type3Loaded) { + return this.type3Loaded; + } + + var translatedFont = this.font; + var loadCharProcsPromise = Promise.resolve(); + var charProcs = this.dict.get('CharProcs').getAll(); + var fontResources = this.dict.get('Resources') || resources; + var charProcKeys = Object.keys(charProcs); + var charProcOperatorList = {}; + for (var i = 0, n = charProcKeys.length; i < n; ++i) { + loadCharProcsPromise = loadCharProcsPromise.then(function (key) { + var glyphStream = charProcs[key]; + var operatorList = new OperatorList(); + return evaluator.getOperatorList(glyphStream, fontResources, + operatorList). + then(function () { + charProcOperatorList[key] = operatorList.getIR(); + + // Add the dependencies to the parent operator list so they are + // resolved before sub operator list is executed synchronously. + parentOperatorList.addDependencies(operatorList.dependencies); + }); + }.bind(this, charProcKeys[i])); + } + this.type3Loaded = loadCharProcsPromise.then(function () { + translatedFont.charProcOperatorList = charProcOperatorList; + }); + return this.type3Loaded; + } + }; + return TranslatedFont; +})(); + var OperatorList = (function OperatorListClosure() { var CHUNK_SIZE = 1000; var CHUNK_SIZE_ABOUT = CHUNK_SIZE - 5; // close to chunk size - function getTransfers(queue) { - var transfers = []; - var fnArray = queue.fnArray, argsArray = queue.argsArray; - for (var i = 0, ii = queue.length; i < ii; i++) { - switch (fnArray[i]) { - case OPS.paintInlineImageXObject: - case OPS.paintInlineImageXObjectGroup: - case OPS.paintImageMaskXObject: - var arg = argsArray[i][0]; // first param in imgData - if (!arg.cached) { - transfers.push(arg.data.buffer); - } - break; - } + function getTransfers(queue) { + var transfers = []; + var fnArray = queue.fnArray, argsArray = queue.argsArray; + for (var i = 0, ii = queue.length; i < ii; i++) { + switch (fnArray[i]) { + case OPS.paintInlineImageXObject: + case OPS.paintInlineImageXObjectGroup: + case OPS.paintImageMaskXObject: + var arg = argsArray[i][0]; // first param in imgData + if (!arg.cached) { + transfers.push(arg.data.buffer); + } + break; } - return transfers; } + return transfers; + } - - function OperatorList(intent, messageHandler, pageIndex) { + function OperatorList(intent, messageHandler, pageIndex) { this.messageHandler = messageHandler; - // When there isn't a message handler the fn array needs to be able to grow - // since we can't flush the operators. - if (messageHandler) { - this.fnArray = new Uint8Array(CHUNK_SIZE); - } else { - this.fnArray = []; - } + this.fnArray = []; this.argsArray = []; this.dependencies = {}; this.pageIndex = pageIndex; - this.fnIndex = 0; this.intent = intent; } OperatorList.prototype = { - get length() { return this.argsArray.length; }, addOp: function(fn, args) { + this.fnArray.push(fn); + this.argsArray.push(args); if (this.messageHandler) { - this.fnArray[this.fnIndex++] = fn; - this.argsArray.push(args); - if (this.fnIndex >= CHUNK_SIZE) { + if (this.fnArray.length >= CHUNK_SIZE) { this.flush(); - } else if (this.fnIndex >= CHUNK_SIZE_ABOUT && - (fn === OPS.restore || fn === OPS.endText)) { + } else if (this.fnArray.length >= CHUNK_SIZE_ABOUT && + (fn === OPS.restore || fn === OPS.endText)) { // heuristic to flush on boundary of restore or endText this.flush(); } - } else { - this.fnArray.push(fn); - this.argsArray.push(args); } }, @@ -16424,75 +17318,103 @@ var OperatorList = (function OperatorListClosure() { }, pageIndex: this.pageIndex, intent: this.intent - }, null, transfers); - this.dependencies = []; - this.fnIndex = 0; - this.argsArray = []; + }, transfers); + this.dependencies = {}; + this.fnArray.length = 0; + this.argsArray.length = 0; } }; return OperatorList; })(); +var StateManager = (function StateManagerClosure() { + function StateManager(initialState) { + this.state = initialState; + this.stateStack = []; + } + StateManager.prototype = { + save: function () { + var old = this.state; + this.stateStack.push(this.state); + this.state = old.clone(); + }, + restore: function () { + var prev = this.stateStack.pop(); + if (prev) { + this.state = prev; + } + }, + transform: function (args) { + this.state.ctm = Util.transform(this.state.ctm, args); + } + }; + return StateManager; +})(); + var TextState = (function TextStateClosure() { function TextState() { + this.ctm = new Float32Array(IDENTITY_MATRIX); this.fontSize = 0; - this.textMatrix = [1, 0, 0, 1, 0, 0]; - this.stateStack = []; - //textState variables + this.font = null; + this.fontMatrix = FONT_IDENTITY_MATRIX; + this.textMatrix = IDENTITY_MATRIX.slice(); + this.textLineMatrix = IDENTITY_MATRIX.slice(); + this.charSpacing = 0; + this.wordSpacing = 0; this.leading = 0; this.textHScale = 1; this.textRise = 0; } + TextState.prototype = { - initialiseTextObj: function TextState_initialiseTextObj() { - var m = this.textMatrix; - m[0] = 1; m[1] = 0; m[2] = 0; m[3] = 1; m[4] = 0; m[5] = 0; - }, setTextMatrix: function TextState_setTextMatrix(a, b, c, d, e, f) { var m = this.textMatrix; m[0] = a; m[1] = b; m[2] = c; m[3] = d; m[4] = e; m[5] = f; }, + setTextLineMatrix: function TextState_setTextMatrix(a, b, c, d, e, f) { + var m = this.textLineMatrix; + m[0] = a; m[1] = b; m[2] = c; m[3] = d; m[4] = e; m[5] = f; + }, translateTextMatrix: function TextState_translateTextMatrix(x, y) { var m = this.textMatrix; m[4] = m[0] * x + m[2] * y + m[4]; m[5] = m[1] * x + m[3] * y + m[5]; }, - calcRenderParams: function TextState_calcRenderingParams(cm) { - var tm = this.textMatrix; - var a = this.fontSize; - var b = a * this.textHScale; - var c = this.textRise; - var vScale = Math.sqrt((tm[2] * tm[2]) + (tm[3] * tm[3])); - var angle = Math.atan2(tm[1], tm[0]); - var m0 = tm[0] * cm[0] + tm[1] * cm[2]; - var m1 = tm[0] * cm[1] + tm[1] * cm[3]; - var m2 = tm[2] * cm[0] + tm[3] * cm[2]; - var m3 = tm[2] * cm[1] + tm[3] * cm[3]; - var m4 = tm[4] * cm[0] + tm[5] * cm[2] + cm[4]; - var m5 = tm[4] * cm[1] + tm[5] * cm[3] + cm[5]; - var renderMatrix = [ - b * m0, - b * m1, - a * m2, - a * m3, - c * m2 + m4, - c * m3 + m5 - ]; - return { - renderMatrix: renderMatrix, - vScale: vScale, - angle: angle - }; + translateTextLineMatrix: function TextState_translateTextMatrix(x, y) { + var m = this.textLineMatrix; + m[4] = m[0] * x + m[2] * y + m[4]; + m[5] = m[1] * x + m[3] * y + m[5]; + }, + calcRenderMatrix: function TextState_calcRendeMatrix(ctm) { + // 9.4.4 Text Space Details + var tsm = [this.fontSize * this.textHScale, 0, + 0, this.fontSize, + 0, this.textRise]; + return Util.transform(ctm, Util.transform(this.textMatrix, tsm)); }, + carriageReturn: function TextState_carriageReturn() { + this.translateTextLineMatrix(0, -this.leading); + this.textMatrix = this.textLineMatrix.slice(); + }, + clone: function TextState_clone() { + var clone = Object.create(this); + clone.textMatrix = this.textMatrix.slice(); + clone.textLineMatrix = this.textLineMatrix.slice(); + clone.fontMatrix = this.fontMatrix.slice(); + return clone; + } }; return TextState; })(); var EvalState = (function EvalStateClosure() { function EvalState() { + this.ctm = new Float32Array(IDENTITY_MATRIX); this.font = null; this.textRenderingMode = TextRenderingMode.FILL; + this.fillColorSpace = ColorSpace.singletons.gray; + this.strokeColorSpace = ColorSpace.singletons.gray; } EvalState.prototype = { clone: function CanvasExtraState_clone() { @@ -16502,7 +17424,7 @@ var EvalState = (function EvalStateClosure() { return EvalState; })(); -var EvaluatorPreprocessor = (function EvaluatorPreprocessor() { +var EvaluatorPreprocessor = (function EvaluatorPreprocessorClosure() { // Specifies properties for each command // // If variableArgs === true: [0, `numArgs`] expected @@ -16562,7 +17484,7 @@ var EvaluatorPreprocessor = (function EvaluatorPreprocessor() { TJ: { id: OPS.showSpacedText, numArgs: 1, variableArgs: false }, '\'': { id: OPS.nextLineShowText, numArgs: 1, variableArgs: false }, '"': { id: OPS.nextLineSetSpacingShowText, numArgs: 3, - variableArgs: false }, + variableArgs: false }, // Type3 fonts d0: { id: OPS.setCharWidth, numArgs: 2, variableArgs: false }, @@ -16596,7 +17518,7 @@ var EvaluatorPreprocessor = (function EvaluatorPreprocessor() { DP: { id: OPS.markPointProps, numArgs: 2, variableArgs: false }, BMC: { id: OPS.beginMarkedContent, numArgs: 1, variableArgs: false }, BDC: { id: OPS.beginMarkedContentProps, numArgs: 2, - variableArgs: false }, + variableArgs: false }, EMC: { id: OPS.endMarkedContent, numArgs: 0, variableArgs: false }, // Compatibility @@ -16616,17 +17538,19 @@ var EvaluatorPreprocessor = (function EvaluatorPreprocessor() { 'null': null }; - function EvaluatorPreprocessor(stream, xref) { + function EvaluatorPreprocessor(stream, xref, stateManager) { // TODO(mduan): pass array of knownCommands rather than OP_MAP // dictionary this.parser = new Parser(new Lexer(stream, OP_MAP), false, xref); - this.ctm = new Float32Array([1, 0, 0, 1, 0, 0]); - this.savedStates = []; + this.stateManager = stateManager; + this.nonProcessedArgs = []; } + EvaluatorPreprocessor.prototype = { get savedStatesDepth() { - return this.savedStates.length; + return this.stateManager.stateStack.length; }, + read: function EvaluatorPreprocessor_read() { var args = []; while (true) { @@ -16637,8 +17561,8 @@ var EvaluatorPreprocessor = (function EvaluatorPreprocessor() { if (!isCmd(obj)) { // argument if (obj !== null && obj !== undefined) { - args.push(obj instanceof Dict ? obj.getAll() : obj); - assertWellFormed(args.length <= 33, 'Too many arguments'); + args.push((obj instanceof Dict ? obj.getAll() : obj)); + assert(args.length <= 33, 'Too many arguments'); } continue; } @@ -16653,64 +17577,56 @@ var EvaluatorPreprocessor = (function EvaluatorPreprocessor() { var fn = opSpec.id; + // Some post script commands can be nested, e.g. /F2 /GS2 gs 5.711 Tf + if (!opSpec.variableArgs && args.length !== opSpec.numArgs) { + while (args.length > opSpec.numArgs) { + this.nonProcessedArgs.push(args.shift()); + } + + while (args.length < opSpec.numArgs && this.nonProcessedArgs.length) { + args.unshift(this.nonProcessedArgs.pop()); + } + } + // Validate the number of arguments for the command if (opSpec.variableArgs) { if (args.length > opSpec.numArgs) { info('Command ' + fn + ': expected [0,' + opSpec.numArgs + - '] args, but received ' + args.length + ' args'); + '] args, but received ' + args.length + ' args'); } } else { if (args.length < opSpec.numArgs) { // If we receive too few args, it's not possible to possible // to execute the command, so skip the command info('Command ' + fn + ': because expected ' + - opSpec.numArgs + ' args, but received ' + args.length + - ' args; skipping'); + opSpec.numArgs + ' args, but received ' + args.length + + ' args; skipping'); args = []; continue; } else if (args.length > opSpec.numArgs) { info('Command ' + fn + ': expected ' + opSpec.numArgs + - ' args, but received ' + args.length + ' args'); + ' args, but received ' + args.length + ' args'); } } // TODO figure out how to type-check vararg functions - this.preprocessCommand(fn, args); - return {fn: fn, args: args}; + return { fn: fn, args: args }; } }, - getState: function EvaluatorPreprocessor_getState() { - return { - ctm: this.ctm - }; - }, - setState: function EvaluatorPreprocessor_setState(state) { - this.ctm = state.ctm; - }, - preprocessCommand: function EvaluatorPreprocessor_preprocessCommand(fn, - args) { + + preprocessCommand: + function EvaluatorPreprocessor_preprocessCommand(fn, args) { switch (fn | 0) { case OPS.save: - this.savedStates.push(this.getState()); + this.stateManager.save(); break; case OPS.restore: - var previousState = this.savedStates.pop(); - if (previousState) { - this.setState(previousState); - } + this.stateManager.restore(); break; case OPS.transform: - var ctm = this.ctm; - var m = new Float32Array(6); - m[0] = ctm[0] * args[0] + ctm[2] * args[1]; - m[1] = ctm[1] * args[0] + ctm[3] * args[1]; - m[2] = ctm[0] * args[2] + ctm[2] * args[3]; - m[3] = ctm[1] * args[2] + ctm[3] * args[3]; - m[4] = ctm[0] * args[4] + ctm[2] * args[5] + ctm[4]; - m[5] = ctm[1] * args[4] + ctm[3] * args[5] + ctm[5]; - this.ctm = m; + this.stateManager.transform(args); break; } } @@ -16719,31 +17635,34 @@ var EvaluatorPreprocessor = (function EvaluatorPreprocessor() { })(); var QueueOptimizer = (function QueueOptimizerClosure() { - function squash(array, index, howMany, element) { - if (isArray(array)) { - array.splice(index, howMany, element); - } else if (typeof element !== 'undefined') { - // Replace the element. - array[index] = element; - // Shift everything after the element up. - var sub = array.subarray(index + howMany); - array.set(sub, index + 1); - } else { - // Shift everything after the element up. - var sub = array.subarray(index + howMany); - array.set(sub, index); - } - } - function addState(parentState, pattern, fn) { var state = parentState; for (var i = 0, ii = pattern.length - 1; i < ii; i++) { var item = pattern[i]; - state = state[item] || (state[item] = []); + state = (state[item] || (state[item] = [])); } state[pattern[pattern.length - 1]] = fn; } + function handlePaintSolidColorImageMask(index, count, fnArray, argsArray) { + // Handles special case of mainly LaTeX documents which + // use image masks to draw lines with the current fill style. + // 'count' groups of (save, transform, paintImageMaskXObject, restore)+ + // have been found at index. + for (var i = 0; i < count; i++) { + var arg = argsArray[index + 4 * i + 2]; + var imageMask = arg.length == 1 && arg[0]; + if (imageMask && imageMask.width == 1 && imageMask.height == 1 && + (!imageMask.data.length || (imageMask.data.length == 1 && + imageMask.data[0] === 0))) { + fnArray[index + 4 * i + 2] = OPS.paintSolidColorImageMask; + continue; + } + break; + } + return count - i; + } + var InitialState = []; addState(InitialState, @@ -16758,10 +17677,9 @@ var QueueOptimizer = (function QueueOptimizerClosure() { var fnArray = context.fnArray, argsArray = context.argsArray; var j = context.currentOperation - 3, i = j + 4; - var ii = context.operationsLength; + var ii = fnArray.length; - for (; i < ii && fnArray[i - 4] === fnArray[i]; i++) { - } + for (; i < ii && fnArray[i - 4] === fnArray[i]; i++) {} var count = Math.min((i - j) >> 2, MAX_IMAGES_IN_INLINE_IMAGES_BLOCK); if (count < MIN_IMAGES_IN_INLINE_IMAGES_BLOCK) { context.currentOperation = i - 1; @@ -16772,7 +17690,8 @@ var QueueOptimizer = (function QueueOptimizerClosure() { var maxX = 0; var map = [], maxLineHeight = 0; var currentX = IMAGE_PADDING, currentY = IMAGE_PADDING; - for (var q = 0; q < count; q++) { + var q; + for (q = 0; q < count; q++) { var transform = argsArray[j + (q << 2) + 1]; var img = argsArray[j + (q << 2) + 2][0]; if (currentX + img.width > MAX_WIDTH) { @@ -16794,22 +17713,19 @@ var QueueOptimizer = (function QueueOptimizerClosure() { var imgHeight = currentY + maxLineHeight + IMAGE_PADDING; var imgData = new Uint8Array(imgWidth * imgHeight * 4); var imgRowSize = imgWidth << 2; - for (var q = 0; q < count; q++) { + for (q = 0; q < count; q++) { var data = argsArray[j + (q << 2) + 2][0].data; // copy image by lines and extends pixels into padding var rowSize = map[q].w << 2; var dataOffset = 0; var offset = (map[q].x + map[q].y * imgWidth) << 2; - imgData.set( - data.subarray(0, rowSize), offset - imgRowSize); + imgData.set(data.subarray(0, rowSize), offset - imgRowSize); for (var k = 0, kk = map[q].h; k < kk; k++) { - imgData.set( - data.subarray(dataOffset, dataOffset + rowSize), offset); + imgData.set(data.subarray(dataOffset, dataOffset + rowSize), offset); dataOffset += rowSize; offset += imgRowSize; } - imgData.set( - data.subarray(dataOffset - rowSize, dataOffset), offset); + imgData.set(data.subarray(dataOffset - rowSize, dataOffset), offset); while (offset >= 0) { data[offset - 4] = data[offset]; data[offset - 3] = data[offset + 1]; @@ -16823,12 +17739,11 @@ var QueueOptimizer = (function QueueOptimizerClosure() { } } // replacing queue items - squash(fnArray, j, count * 4, OPS.paintInlineImageXObjectGroup); + fnArray.splice(j, count * 4, OPS.paintInlineImageXObjectGroup); argsArray.splice(j, count * 4, - [{width: imgWidth, height: imgHeight, kind: ImageKind.RGBA_32BPP, - data: imgData}, map]); + [{ width: imgWidth, height: imgHeight, kind: ImageKind.RGBA_32BPP, + data: imgData }, map]); context.currentOperation = j; - context.operationsLength -= count * 4 - 1; }); addState(InitialState, @@ -16842,23 +17757,24 @@ var QueueOptimizer = (function QueueOptimizerClosure() { var fnArray = context.fnArray, argsArray = context.argsArray; var j = context.currentOperation - 3, i = j + 4; - var ii = context.operationsLength; + var ii = fnArray.length, q; - for (; i < ii && fnArray[i - 4] === fnArray[i]; i++) { - } + for (; i < ii && fnArray[i - 4] === fnArray[i]; i++) {} var count = (i - j) >> 2; + count = handlePaintSolidColorImageMask(j, count, fnArray, argsArray); if (count < MIN_IMAGES_IN_MASKS_BLOCK) { context.currentOperation = i - 1; return; } var isSameImage = false; + var transformArgs; if (argsArray[j + 1][1] === 0 && argsArray[j + 1][2] === 0) { i = j + 4; isSameImage = true; - for (var q = 1; q < count; q++, i += 4) { + for (q = 1; q < count; q++, i += 4) { var prevTransformArgs = argsArray[i - 3]; - var transformArgs = argsArray[i + 1]; + transformArgs = argsArray[i + 1]; if (argsArray[i - 2][0] !== argsArray[i + 2][0] || prevTransformArgs[0] !== transformArgs[0] || prevTransformArgs[1] !== transformArgs[1] || @@ -16878,36 +17794,35 @@ var QueueOptimizer = (function QueueOptimizerClosure() { count = Math.min(count, MAX_SAME_IMAGES_IN_MASKS_BLOCK); var positions = new Float32Array(count * 2); i = j + 1; - for (var q = 0; q < count; q++) { - var transformArgs = argsArray[i]; + for (q = 0; q < count; q++) { + transformArgs = argsArray[i]; positions[(q << 1)] = transformArgs[4]; positions[(q << 1) + 1] = transformArgs[5]; i += 4; } // replacing queue items - squash(fnArray, j, count * 4, OPS.paintImageMaskXObjectRepeat); + fnArray.splice(j, count * 4, OPS.paintImageMaskXObjectRepeat); argsArray.splice(j, count * 4, [argsArray[j + 2][0], - argsArray[j + 1][0], argsArray[j + 1][3], positions]); + argsArray[j + 1][0], argsArray[j + 1][3], positions]); context.currentOperation = j; - context.operationsLength -= count * 4 - 1; } else { count = Math.min(count, MAX_IMAGES_IN_MASKS_BLOCK); var images = []; - for (var q = 0; q < count; q++) { - var transformArgs = argsArray[j + (q << 2) + 1]; + for (q = 0; q < count; q++) { + transformArgs = argsArray[j + (q << 2) + 1]; var maskParams = argsArray[j + (q << 2) + 2][0]; - images.push({data: maskParams.data, width: maskParams.width, - height: maskParams.height, transform: transformArgs}); + images.push({ data: maskParams.data, width: maskParams.width, + height: maskParams.height, + transform: transformArgs }); } // replacing queue items - squash(fnArray, j, count * 4, OPS.paintImageMaskXObjectGroup); + fnArray.splice(j, count * 4, OPS.paintImageMaskXObjectGroup); argsArray.splice(j, count * 4, [images]); context.currentOperation = j; - context.operationsLength -= count * 4 - 1; } }); @@ -16922,7 +17837,8 @@ var QueueOptimizer = (function QueueOptimizerClosure() { if (argsArray[j + 1][1] !== 0 || argsArray[j + 1][2] !== 0) { return; } - var ii = context.operationsLength; + var ii = fnArray.length; + var transformArgs; for (; i + 3 < ii && fnArray[i - 4] === fnArray[i]; i += 4) { if (fnArray[i - 3] !== fnArray[i + 1] || fnArray[i - 2] !== fnArray[i + 2] || @@ -16933,7 +17849,7 @@ var QueueOptimizer = (function QueueOptimizerClosure() { break; // different image } var prevTransformArgs = argsArray[i - 3]; - var transformArgs = argsArray[i + 1]; + transformArgs = argsArray[i + 1]; if (prevTransformArgs[0] !== transformArgs[0] || prevTransformArgs[1] !== transformArgs[1] || prevTransformArgs[2] !== transformArgs[2] || @@ -16950,19 +17866,18 @@ var QueueOptimizer = (function QueueOptimizerClosure() { var positions = new Float32Array(count * 2); i = j + 1; for (var q = 0; q < count; q++) { - var transformArgs = argsArray[i]; + transformArgs = argsArray[i]; positions[(q << 1)] = transformArgs[4]; positions[(q << 1) + 1] = transformArgs[5]; i += 4; } var args = [argsArray[j + 2][0], argsArray[j + 1][0], - argsArray[j + 1][3], positions]; + argsArray[j + 1][3], positions]; // replacing queue items - squash(fnArray, j, count * 4, OPS.paintImageXObjectRepeat); + fnArray.splice(j, count * 4, OPS.paintImageXObjectRepeat); argsArray.splice(j, count * 4, args); context.currentOperation = j; - context.operationsLength -= count * 4 - 1; }); addState(InitialState, @@ -16975,12 +17890,12 @@ var QueueOptimizer = (function QueueOptimizerClosure() { var fnArray = context.fnArray, argsArray = context.argsArray; var j = context.currentOperation - 4, i = j + 5; - var ii = context.operationsLength; + var ii = fnArray.length; for (; i < ii && fnArray[i - 5] === fnArray[i]; i++) { if (fnArray[i] === OPS.setFont) { if (argsArray[i - 5][0] !== argsArray[i][0] || - argsArray[i - 5][1] !== argsArray[i][1]) { + argsArray[i - 5][1] !== argsArray[i][1]) { break; } } @@ -16991,11 +17906,11 @@ var QueueOptimizer = (function QueueOptimizerClosure() { return; } if (j >= 4 && fnArray[j - 4] === fnArray[j + 1] && - fnArray[j - 3] === fnArray[j + 2] && - fnArray[j - 2] === fnArray[j + 3] && - fnArray[j - 1] === fnArray[j + 4] && - argsArray[j - 4][0] === argsArray[j + 1][0] && - argsArray[j - 4][1] === argsArray[j + 1][1]) { + fnArray[j - 3] === fnArray[j + 2] && + fnArray[j - 2] === fnArray[j + 3] && + fnArray[j - 1] === fnArray[j + 4] && + argsArray[j - 4][0] === argsArray[j + 1][0] && + argsArray[j - 4][1] === argsArray[j + 1][1]) { // extending one block ahead (very first block might have 'dependency') count++; j -= 5; @@ -17011,22 +17926,19 @@ var QueueOptimizer = (function QueueOptimizerClosure() { k += 5; } var removed = (count - 1) * 3; - squash(fnArray, i, removed); + fnArray.splice(i, removed); argsArray.splice(i, removed); context.currentOperation = i; - context.operationsLength -= removed; - }); - function QueueOptimizer() { - } + function QueueOptimizer() {} + QueueOptimizer.prototype = { optimize: function QueueOptimizer_optimize(queue) { var fnArray = queue.fnArray, argsArray = queue.argsArray; var context = { currentOperation: 0, - operationsLength: argsArray.length, fnArray: fnArray, argsArray: argsArray }; @@ -17038,7 +17950,7 @@ var QueueOptimizer = (function QueueOptimizerClosure() { context.currentOperation = i; state = state(context); i = context.currentOperation; - ii = context.operationsLength; + ii = context.fnArray.length; } } } @@ -17047,6 +17959,841 @@ var QueueOptimizer = (function QueueOptimizerClosure() { })(); +var BUILT_IN_CMAPS = [ +// << Start unicode maps. +'Adobe-GB1-UCS2', +'Adobe-CNS1-UCS2', +'Adobe-Japan1-UCS2', +'Adobe-Korea1-UCS2', +// >> End unicode maps. +'78-EUC-H', +'78-EUC-V', +'78-H', +'78-RKSJ-H', +'78-RKSJ-V', +'78-V', +'78ms-RKSJ-H', +'78ms-RKSJ-V', +'83pv-RKSJ-H', +'90ms-RKSJ-H', +'90ms-RKSJ-V', +'90msp-RKSJ-H', +'90msp-RKSJ-V', +'90pv-RKSJ-H', +'90pv-RKSJ-V', +'Add-H', +'Add-RKSJ-H', +'Add-RKSJ-V', +'Add-V', +'Adobe-CNS1-0', +'Adobe-CNS1-1', +'Adobe-CNS1-2', +'Adobe-CNS1-3', +'Adobe-CNS1-4', +'Adobe-CNS1-5', +'Adobe-CNS1-6', +'Adobe-GB1-0', +'Adobe-GB1-1', +'Adobe-GB1-2', +'Adobe-GB1-3', +'Adobe-GB1-4', +'Adobe-GB1-5', +'Adobe-Japan1-0', +'Adobe-Japan1-1', +'Adobe-Japan1-2', +'Adobe-Japan1-3', +'Adobe-Japan1-4', +'Adobe-Japan1-5', +'Adobe-Japan1-6', +'Adobe-Korea1-0', +'Adobe-Korea1-1', +'Adobe-Korea1-2', +'B5-H', +'B5-V', +'B5pc-H', +'B5pc-V', +'CNS-EUC-H', +'CNS-EUC-V', +'CNS1-H', +'CNS1-V', +'CNS2-H', +'CNS2-V', +'ETHK-B5-H', +'ETHK-B5-V', +'ETen-B5-H', +'ETen-B5-V', +'ETenms-B5-H', +'ETenms-B5-V', +'EUC-H', +'EUC-V', +'Ext-H', +'Ext-RKSJ-H', +'Ext-RKSJ-V', +'Ext-V', +'GB-EUC-H', +'GB-EUC-V', +'GB-H', +'GB-V', +'GBK-EUC-H', +'GBK-EUC-V', +'GBK2K-H', +'GBK2K-V', +'GBKp-EUC-H', +'GBKp-EUC-V', +'GBT-EUC-H', +'GBT-EUC-V', +'GBT-H', +'GBT-V', +'GBTpc-EUC-H', +'GBTpc-EUC-V', +'GBpc-EUC-H', +'GBpc-EUC-V', +'H', +'HKdla-B5-H', +'HKdla-B5-V', +'HKdlb-B5-H', +'HKdlb-B5-V', +'HKgccs-B5-H', +'HKgccs-B5-V', +'HKm314-B5-H', +'HKm314-B5-V', +'HKm471-B5-H', +'HKm471-B5-V', +'HKscs-B5-H', +'HKscs-B5-V', +'Hankaku', +'Hiragana', +'KSC-EUC-H', +'KSC-EUC-V', +'KSC-H', +'KSC-Johab-H', +'KSC-Johab-V', +'KSC-V', +'KSCms-UHC-H', +'KSCms-UHC-HW-H', +'KSCms-UHC-HW-V', +'KSCms-UHC-V', +'KSCpc-EUC-H', +'KSCpc-EUC-V', +'Katakana', +'NWP-H', +'NWP-V', +'RKSJ-H', +'RKSJ-V', +'Roman', +'UniCNS-UCS2-H', +'UniCNS-UCS2-V', +'UniCNS-UTF16-H', +'UniCNS-UTF16-V', +'UniCNS-UTF32-H', +'UniCNS-UTF32-V', +'UniCNS-UTF8-H', +'UniCNS-UTF8-V', +'UniGB-UCS2-H', +'UniGB-UCS2-V', +'UniGB-UTF16-H', +'UniGB-UTF16-V', +'UniGB-UTF32-H', +'UniGB-UTF32-V', +'UniGB-UTF8-H', +'UniGB-UTF8-V', +'UniJIS-UCS2-H', +'UniJIS-UCS2-HW-H', +'UniJIS-UCS2-HW-V', +'UniJIS-UCS2-V', +'UniJIS-UTF16-H', +'UniJIS-UTF16-V', +'UniJIS-UTF32-H', +'UniJIS-UTF32-V', +'UniJIS-UTF8-H', +'UniJIS-UTF8-V', +'UniJIS2004-UTF16-H', +'UniJIS2004-UTF16-V', +'UniJIS2004-UTF32-H', +'UniJIS2004-UTF32-V', +'UniJIS2004-UTF8-H', +'UniJIS2004-UTF8-V', +'UniJISPro-UCS2-HW-V', +'UniJISPro-UCS2-V', +'UniJISPro-UTF8-V', +'UniJISX0213-UTF32-H', +'UniJISX0213-UTF32-V', +'UniJISX02132004-UTF32-H', +'UniJISX02132004-UTF32-V', +'UniKS-UCS2-H', +'UniKS-UCS2-V', +'UniKS-UTF16-H', +'UniKS-UTF16-V', +'UniKS-UTF32-H', +'UniKS-UTF32-V', +'UniKS-UTF8-H', +'UniKS-UTF8-V', +'V', +'WP-Symbol']; + +// CMap, not to be confused with TrueType's cmap. +var CMap = (function CMapClosure() { + function CMap(builtInCMap) { + // Codespace ranges are stored as follows: + // [[1BytePairs], [2BytePairs], [3BytePairs], [4BytePairs]] + // where nBytePairs are ranges e.g. [low1, high1, low2, high2, ...] + this.codespaceRanges = [[], [], [], []]; + this.numCodespaceRanges = 0; + this.map = []; + this.vertical = false; + this.useCMap = null; + this.builtInCMap = builtInCMap; + } + CMap.prototype = { + addCodespaceRange: function(n, low, high) { + this.codespaceRanges[n - 1].push(low, high); + this.numCodespaceRanges++; + }, + + mapRange: function(low, high, dstLow) { + var lastByte = dstLow.length - 1; + while (low <= high) { + this.map[low] = dstLow; + // Only the last byte has to be incremented. + dstLow = dstLow.substr(0, lastByte) + + String.fromCharCode(dstLow.charCodeAt(lastByte) + 1); + ++low; + } + }, + + mapRangeToArray: function(low, high, array) { + var i = 0; + while (low <= high) { + this.map[low] = array[i++]; + ++low; + } + }, + + mapOne: function(src, dst) { + this.map[src] = dst; + }, + + lookup: function(code) { + return this.map[code]; + }, + + readCharCode: function(str, offset) { + var c = 0; + var codespaceRanges = this.codespaceRanges; + var codespaceRangesLen = this.codespaceRanges.length; + // 9.7.6.2 CMap Mapping + // The code length is at most 4. + for (var n = 0; n < codespaceRangesLen; n++) { + c = ((c << 8) | str.charCodeAt(offset + n)) >>> 0; + // Check each codespace range to see if it falls within. + var codespaceRange = codespaceRanges[n]; + for (var k = 0, kk = codespaceRange.length; k < kk;) { + var low = codespaceRange[k++]; + var high = codespaceRange[k++]; + if (c >= low && c <= high) { + return [c, n + 1]; + } + } + } + + return [0, 1]; + } + + }; + return CMap; +})(); + +var IdentityCMap = (function IdentityCMapClosure() { + function IdentityCMap(vertical, n) { + CMap.call(this); + this.vertical = vertical; + this.addCodespaceRange(n, 0, 0xffff); + this.mapRange(0, 0xffff, '\u0000'); + } + Util.inherit(IdentityCMap, CMap, {}); + + return IdentityCMap; +})(); + +var BinaryCMapReader = (function BinaryCMapReaderClosure() { + function fetchBinaryData(url) { + var nonBinaryRequest = PDFJS.disableWorker; + var request = new XMLHttpRequest(); + request.open('GET', url, false); + if (!nonBinaryRequest) { + try { + request.responseType = 'arraybuffer'; + nonBinaryRequest = request.responseType !== 'arraybuffer'; + } catch (e) { + nonBinaryRequest = true; + } + } + if (nonBinaryRequest && request.overrideMimeType) { + request.overrideMimeType('text/plain; charset=x-user-defined'); + } + request.send(null); + if (request.status === 0 && /^https?:/i.test(url)) { + error('Unable to get binary cMap at: ' + url); + } + if (nonBinaryRequest) { + var data = Array.prototype.map.call(request.responseText, function (ch) { + return ch.charCodeAt(0) & 255; + }); + return new Uint8Array(data); + } + return new Uint8Array(request.response); + } + + function hexToInt(a, size) { + var n = 0; + for (var i = 0; i <= size; i++) { + n = (n << 8) | a[i]; + } + return n >>> 0; + } + + function hexToStr(a, size) { + return String.fromCharCode.apply(null, a.subarray(0, size + 1)); + } + + function addHex(a, b, size) { + var c = 0; + for (var i = size; i >= 0; i--) { + c += a[i] + b[i]; + a[i] = c & 255; + c >>= 8; + } + } + + function incHex(a, size) { + var c = 1; + for (var i = size; i >= 0 && c > 0; i--) { + c += a[i]; + a[i] = c & 255; + c >>= 8; + } + } + + var MAX_NUM_SIZE = 16; + var MAX_ENCODED_NUM_SIZE = 19; // ceil(MAX_NUM_SIZE * 7 / 8) + + function BinaryCMapStream(data) { + this.buffer = data; + this.pos = 0; + this.end = data.length; + this.tmpBuf = new Uint8Array(MAX_ENCODED_NUM_SIZE); + } + + BinaryCMapStream.prototype = { + readByte: function () { + if (this.pos >= this.end) { + return -1; + } + return this.buffer[this.pos++]; + }, + readNumber: function () { + var n = 0; + var last; + do { + var b = this.readByte(); + if (b < 0) { + error('unexpected EOF in bcmap'); + } + last = !(b & 0x80); + n = (n << 7) | (b & 0x7F); + } while (!last); + return n; + }, + readSigned: function () { + var n = this.readNumber(); + return (n & 1) ? ~(n >>> 1) : n >>> 1; + }, + readHex: function (num, size) { + num.set(this.buffer.subarray(this.pos, + this.pos + size + 1)); + this.pos += size + 1; + }, + readHexNumber: function (num, size) { + var last; + var stack = this.tmpBuf, sp = 0; + do { + var b = this.readByte(); + if (b < 0) { + error('unexpected EOF in bcmap'); + } + last = !(b & 0x80); + stack[sp++] = b & 0x7F; + } while (!last); + var i = size, buffer = 0, bufferSize = 0; + while (i >= 0) { + while (bufferSize < 8 && stack.length > 0) { + buffer = (stack[--sp] << bufferSize) | buffer; + bufferSize += 7; + } + num[i] = buffer & 255; + i--; + buffer >>= 8; + bufferSize -= 8; + } + }, + readHexSigned: function (num, size) { + this.readHexNumber(num, size); + var sign = num[size] & 1 ? 255 : 0; + var c = 0; + for (var i = 0; i <= size; i++) { + c = ((c & 1) << 8) | num[i]; + num[i] = (c >> 1) ^ sign; + } + }, + readString: function () { + var len = this.readNumber(); + var s = ''; + for (var i = 0; i < len; i++) { + s += String.fromCharCode(this.readNumber()); + } + return s; + } + }; + + function processBinaryCMap(url, cMap, extend) { + var data = fetchBinaryData(url); + var stream = new BinaryCMapStream(data); + + var header = stream.readByte(); + cMap.vertical = !!(header & 1); + + var useCMap = null; + var start = new Uint8Array(MAX_NUM_SIZE); + var end = new Uint8Array(MAX_NUM_SIZE); + var char = new Uint8Array(MAX_NUM_SIZE); + var charCode = new Uint8Array(MAX_NUM_SIZE); + var tmp = new Uint8Array(MAX_NUM_SIZE); + var code; + + var b; + while ((b = stream.readByte()) >= 0) { + var type = b >> 5; + if (type === 7) { // metadata, e.g. comment or usecmap + switch (b & 0x1F) { + case 0: + stream.readString(); // skipping comment + break; + case 1: + useCMap = stream.readString(); + break; + } + continue; + } + var sequence = !!(b & 0x10); + var dataSize = b & 15; + + assert(dataSize + 1 <= MAX_NUM_SIZE); + + var ucs2DataSize = 1; + var subitemsCount = stream.readNumber(); + var i; + switch (type) { + case 0: // codespacerange + stream.readHex(start, dataSize); + stream.readHexNumber(end, dataSize); + addHex(end, start, dataSize); + cMap.addCodespaceRange(dataSize + 1, hexToInt(start, dataSize), + hexToInt(end, dataSize)); + for (i = 1; i < subitemsCount; i++) { + incHex(end, dataSize); + stream.readHexNumber(start, dataSize); + addHex(start, end, dataSize); + stream.readHexNumber(end, dataSize); + addHex(end, start, dataSize); + cMap.addCodespaceRange(dataSize + 1, hexToInt(start, dataSize), + hexToInt(end, dataSize)); + } + break; + case 1: // notdefrange + stream.readHex(start, dataSize); + stream.readHexNumber(end, dataSize); + addHex(end, start, dataSize); + code = stream.readNumber(); + // undefined range, skipping + for (i = 1; i < subitemsCount; i++) { + incHex(end, dataSize); + stream.readHexNumber(start, dataSize); + addHex(start, end, dataSize); + stream.readHexNumber(end, dataSize); + addHex(end, start, dataSize); + code = stream.readNumber(); + // nop + } + break; + case 2: // cidchar + stream.readHex(char, dataSize); + code = stream.readNumber(); + cMap.mapOne(hexToInt(char, dataSize), String.fromCharCode(code)); + for (i = 1; i < subitemsCount; i++) { + incHex(char, dataSize); + if (!sequence) { + stream.readHexNumber(tmp, dataSize); + addHex(char, tmp, dataSize); + } + code = stream.readSigned() + (code + 1); + cMap.mapOne(hexToInt(char, dataSize), String.fromCharCode(code)); + } + break; + case 3: // cidrange + stream.readHex(start, dataSize); + stream.readHexNumber(end, dataSize); + addHex(end, start, dataSize); + code = stream.readNumber(); + cMap.mapRange(hexToInt(start, dataSize), hexToInt(end, dataSize), + String.fromCharCode(code)); + for (i = 1; i < subitemsCount; i++) { + incHex(end, dataSize); + if (!sequence) { + stream.readHexNumber(start, dataSize); + addHex(start, end, dataSize); + } else { + start.set(end); + } + stream.readHexNumber(end, dataSize); + addHex(end, start, dataSize); + code = stream.readNumber(); + cMap.mapRange(hexToInt(start, dataSize), hexToInt(end, dataSize), + String.fromCharCode(code)); + } + break; + case 4: // bfchar + stream.readHex(char, ucs2DataSize); + stream.readHex(charCode, dataSize); + cMap.mapOne(hexToInt(char, ucs2DataSize), + hexToStr(charCode, dataSize)); + for (i = 1; i < subitemsCount; i++) { + incHex(char, ucs2DataSize); + if (!sequence) { + stream.readHexNumber(tmp, ucs2DataSize); + addHex(char, tmp, ucs2DataSize); + } + incHex(charCode, dataSize); + stream.readHexSigned(tmp, dataSize); + addHex(charCode, tmp, dataSize); + cMap.mapOne(hexToInt(char, ucs2DataSize), + hexToStr(charCode, dataSize)); + } + break; + case 5: // bfrange + stream.readHex(start, ucs2DataSize); + stream.readHexNumber(end, ucs2DataSize); + addHex(end, start, ucs2DataSize); + stream.readHex(charCode, dataSize); + cMap.mapRange(hexToInt(start, ucs2DataSize), + hexToInt(end, ucs2DataSize), + hexToStr(charCode, dataSize)); + for (i = 1; i < subitemsCount; i++) { + incHex(end, ucs2DataSize); + if (!sequence) { + stream.readHexNumber(start, ucs2DataSize); + addHex(start, end, ucs2DataSize); + } else { + start.set(end); + } + stream.readHexNumber(end, ucs2DataSize); + addHex(end, start, ucs2DataSize); + stream.readHex(charCode, dataSize); + cMap.mapRange(hexToInt(start, ucs2DataSize), + hexToInt(end, ucs2DataSize), + hexToStr(charCode, dataSize)); + } + break; + default: + error('Unknown type: ' + type); + break; + } + } + + if (useCMap) { + extend(useCMap); + } + return cMap; + } + + function BinaryCMapReader() {} + + BinaryCMapReader.prototype = { + read: processBinaryCMap + }; + + return BinaryCMapReader; +})(); + +var CMapFactory = (function CMapFactoryClosure() { + function strToInt(str) { + var a = 0; + for (var i = 0; i < str.length; i++) { + a = (a << 8) | str.charCodeAt(i); + } + return a >>> 0; + } + + function expectString(obj) { + if (!isString(obj)) { + error('Malformed CMap: expected string.'); + } + } + + function expectInt(obj) { + if (!isInt(obj)) { + error('Malformed CMap: expected int.'); + } + } + + function parseBfChar(cMap, lexer) { + while (true) { + var obj = lexer.getObj(); + if (isEOF(obj)) { + break; + } + if (isCmd(obj, 'endbfchar')) { + return; + } + expectString(obj); + var src = strToInt(obj); + obj = lexer.getObj(); + // TODO are /dstName used? + expectString(obj); + var dst = obj; + cMap.mapOne(src, dst); + } + } + + function parseBfRange(cMap, lexer) { + while (true) { + var obj = lexer.getObj(); + if (isEOF(obj)) { + break; + } + if (isCmd(obj, 'endbfrange')) { + return; + } + expectString(obj); + var low = strToInt(obj); + obj = lexer.getObj(); + expectString(obj); + var high = strToInt(obj); + obj = lexer.getObj(); + if (isInt(obj) || isString(obj)) { + var dstLow = isInt(obj) ? String.fromCharCode(obj) : obj; + cMap.mapRange(low, high, dstLow); + } else if (isCmd(obj, '[')) { + obj = lexer.getObj(); + var array = []; + while (!isCmd(obj, ']') && !isEOF(obj)) { + array.push(obj); + obj = lexer.getObj(); + } + cMap.mapRangeToArray(low, high, array); + } else { + break; + } + } + error('Invalid bf range.'); + } + + function parseCidChar(cMap, lexer) { + while (true) { + var obj = lexer.getObj(); + if (isEOF(obj)) { + break; + } + if (isCmd(obj, 'endcidchar')) { + return; + } + expectString(obj); + var src = strToInt(obj); + obj = lexer.getObj(); + expectInt(obj); + var dst = String.fromCharCode(obj); + cMap.mapOne(src, dst); + } + } + + function parseCidRange(cMap, lexer) { + while (true) { + var obj = lexer.getObj(); + if (isEOF(obj)) { + break; + } + if (isCmd(obj, 'endcidrange')) { + return; + } + expectString(obj); + var low = strToInt(obj); + obj = lexer.getObj(); + expectString(obj); + var high = strToInt(obj); + obj = lexer.getObj(); + expectInt(obj); + var dstLow = String.fromCharCode(obj); + cMap.mapRange(low, high, dstLow); + } + } + + function parseCodespaceRange(cMap, lexer) { + while (true) { + var obj = lexer.getObj(); + if (isEOF(obj)) { + break; + } + if (isCmd(obj, 'endcodespacerange')) { + return; + } + if (!isString(obj)) { + break; + } + var low = strToInt(obj); + obj = lexer.getObj(); + if (!isString(obj)) { + break; + } + var high = strToInt(obj); + cMap.addCodespaceRange(obj.length, low, high); + } + error('Invalid codespace range.'); + } + + function parseWMode(cMap, lexer) { + var obj = lexer.getObj(); + if (isInt(obj)) { + cMap.vertical = !!obj; + } + } + + function parseCMap(cMap, lexer, builtInCMapParams, useCMap) { + var previous; + var embededUseCMap; + objLoop: while (true) { + var obj = lexer.getObj(); + if (isEOF(obj)) { + break; + } else if (isName(obj)) { + if (obj.name === 'WMode') { + parseWMode(cMap, lexer); + } + previous = obj; + } else if (isCmd(obj)) { + switch (obj.cmd) { + case 'endcmap': + break objLoop; + case 'usecmap': + if (isName(previous)) { + embededUseCMap = previous.name; + } + break; + case 'begincodespacerange': + parseCodespaceRange(cMap, lexer); + break; + case 'beginbfchar': + parseBfChar(cMap, lexer); + break; + case 'begincidchar': + parseCidChar(cMap, lexer); + break; + case 'beginbfrange': + parseBfRange(cMap, lexer); + break; + case 'begincidrange': + parseCidRange(cMap, lexer); + break; + } + } + } + + if (!useCMap && embededUseCMap) { + // Load the usecmap definition from the file only if there wasn't one + // specified. + useCMap = embededUseCMap; + } + if (useCMap) { + extendCMap(cMap, builtInCMapParams, useCMap); + } + } + + function extendCMap(cMap, builtInCMapParams, useCMap) { + cMap.useCMap = createBuiltInCMap(useCMap, builtInCMapParams); + // If there aren't any code space ranges defined clone all the parent ones + // into this cMap. + if (cMap.numCodespaceRanges === 0) { + var useCodespaceRanges = cMap.useCMap.codespaceRanges; + for (var i = 0; i < useCodespaceRanges.length; i++) { + cMap.codespaceRanges[i] = useCodespaceRanges[i].slice(); + } + cMap.numCodespaceRanges = cMap.useCMap.numCodespaceRanges; + } + // Merge the map into the current one, making sure not to override + // any previously defined entries. + for (var key in cMap.useCMap.map) { + if (key in cMap.map) { + continue; + } + cMap.map[key] = cMap.useCMap.map[key]; + } + } + + function parseBinaryCMap(name, builtInCMapParams) { + var url = builtInCMapParams.url + name + '.bcmap'; + var cMap = new CMap(true); + new BinaryCMapReader().read(url, cMap, function (useCMap) { + extendCMap(cMap, builtInCMapParams, useCMap); + }); + return cMap; + } + + function createBuiltInCMap(name, builtInCMapParams) { + if (name === 'Identity-H') { + return new IdentityCMap(false, 2); + } else if (name === 'Identity-V') { + return new IdentityCMap(true, 2); + } + if (BUILT_IN_CMAPS.indexOf(name) === -1) { + error('Unknown cMap name: ' + name); + } + assert (builtInCMapParams, 'buildin cmap parameters are not provided'); + + if (builtInCMapParams.packed) { + return parseBinaryCMap(name, builtInCMapParams); + } + + var request = new XMLHttpRequest(); + var url = builtInCMapParams.url + name; + request.open('GET', url, false); + request.send(null); + if (request.status === 0 && /^https?:/i.test(url)) { + error('Unable to get cMap at: ' + url); + } + var cMap = new CMap(true); + var lexer = new Lexer(new StringStream(request.responseText)); + parseCMap(cMap, lexer, builtInCMapParams, null); + return cMap; + } + + return { + create: function (encoding, builtInCMapParams, useCMap) { + if (isName(encoding)) { + return createBuiltInCMap(encoding.name, builtInCMapParams); + } else if (isStream(encoding)) { + var cMap = new CMap(); + var lexer = new Lexer(encoding); + try { + parseCMap(cMap, lexer, builtInCMapParams, useCMap); + } catch (e) { + warn('Invalid CMap data. ' + e); + } + return cMap; + } + error('Encoding required.'); + } + }; +})(); + + // Unicode Private Use Area var PRIVATE_USE_OFFSET_START = 0xE000; var PRIVATE_USE_OFFSET_END = 0xF8FF; @@ -17182,7 +18929,7 @@ var Encodings = { 'summation', 'product', 'pi', 'integral', 'ordfeminine', 'ordmasculine', 'Omega', 'ae', 'oslash', 'questiondown', 'exclamdown', 'logicalnot', 'radical', 'florin', 'approxequal', 'Delta', 'guillemotleft', - 'guillemotright', 'ellipsis', '', 'Agrave', 'Atilde', 'Otilde', 'OE', + 'guillemotright', 'ellipsis', 'space', 'Agrave', 'Atilde', 'Otilde', 'OE', 'oe', 'endash', 'emdash', 'quotedblleft', 'quotedblright', 'quoteleft', 'quoteright', 'divide', 'lozenge', 'ydieresis', 'Ydieresis', 'fraction', 'currency', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'daggerdbl', @@ -17234,7 +18981,7 @@ var Encodings = { 'guilsinglleft', 'OE', 'bullet', 'Zcaron', 'bullet', 'bullet', 'quoteleft', 'quoteright', 'quotedblleft', 'quotedblright', 'bullet', 'endash', 'emdash', 'tilde', 'trademark', 'scaron', 'guilsinglright', 'oe', 'bullet', - 'zcaron', 'Ydieresis', '', 'exclamdown', 'cent', 'sterling', + 'zcaron', 'Ydieresis', 'space', 'exclamdown', 'cent', 'sterling', 'currency', 'yen', 'brokenbar', 'section', 'dieresis', 'copyright', 'ordfeminine', 'guillemotleft', 'logicalnot', 'hyphen', 'registered', 'macron', 'degree', 'plusminus', 'twosuperior', 'threesuperior', 'acute', @@ -17396,7 +19143,7 @@ var nonStdFontMap = { 'MS-PMincho': 'MS PMincho', 'MS-PMincho-Bold': 'MS PMincho-Bold', 'MS-PMincho-BoldItalic': 'MS PMincho-BoldItalic', - 'MS-PMincho-Italic': 'MS PMincho-Italic', + 'MS-PMincho-Italic': 'MS PMincho-Italic' }; var serifFonts = { @@ -17455,7 +19202,7 @@ var symbolsFonts = { // glyphs, but common for some set of the standard fonts. var GlyphMapForStandardFonts = { '2': 10, '3': 32, '4': 33, '5': 34, '6': 35, '7': 36, '8': 37, '9': 38, - '10': 39, '11': 40, '12': 41, '13': 42, '14': 43, '15': 44, '16': 173, + '10': 39, '11': 40, '12': 41, '13': 42, '14': 43, '15': 44, '16': 45, '17': 46, '18': 47, '19': 48, '20': 49, '21': 50, '22': 51, '23': 52, '24': 53, '25': 54, '26': 55, '27': 56, '28': 57, '29': 58, '30': 894, '31': 60, '32': 61, '33': 62, '34': 63, '35': 64, '36': 65, '37': 66, @@ -17479,7 +19226,9 @@ var GlyphMapForStandardFonts = { '149': 8805, '150': 165, '151': 181, '152': 8706, '153': 8721, '154': 8719, '156': 8747, '157': 170, '158': 186, '159': 8486, '160': 230, '161': 248, '162': 191, '163': 161, '164': 172, '165': 8730, '166': 402, '167': 8776, - '168': 8710, '169': 171, '170': 187, '171': 8230, '210': 218, '305': 963, + '168': 8710, '169': 171, '170': 187, '171': 8230, '210': 218, '223': 711, + '227': 353, '229': 382, '234': 253, '253': 268, '254': 269, '258': 258, + '268': 283, '269': 313, '278': 328, '284': 345, '292': 367, '305': 963, '306': 964, '307': 966, '308': 8215, '309': 8252, '310': 8319, '311': 8359, '312': 8592, '313': 8593, '337': 9552, '493': 1039, '494': 1040, '705': 1524, '706': 8362, '710': 64288, '711': 64298, '759': 1617, '761': 1776, @@ -17712,19 +19461,22 @@ var MacStandardGlyphOrdering = [ function getUnicodeRangeFor(value) { for (var i = 0, ii = UnicodeRanges.length; i < ii; i++) { var range = UnicodeRanges[i]; - if (value >= range.begin && value < range.end) + if (value >= range.begin && value < range.end) { return i; + } } return -1; } function isRTLRangeFor(value) { var range = UnicodeRanges[13]; - if (value >= range.begin && value < range.end) + if (value >= range.begin && value < range.end) { return true; + } range = UnicodeRanges[11]; - if (value >= range.begin && value < range.end) + if (value >= range.begin && value < range.end) { return true; + } return false; } @@ -19113,29 +20865,14 @@ var NormalizedUnicodes = { function reverseIfRtl(chars) { var charsLength = chars.length; //reverse an arabic ligature - if (charsLength <= 1 || !isRTLRangeFor(chars.charCodeAt(0))) + if (charsLength <= 1 || !isRTLRangeFor(chars.charCodeAt(0))) { return chars; - + } var s = ''; - for (var ii = charsLength - 1; ii >= 0; ii--) + for (var ii = charsLength - 1; ii >= 0; ii--) { s += chars[ii]; - return s; -} - -function fontCharsToUnicode(charCodes, font) { - var glyphs = font.charsToGlyphs(charCodes); - var result = ''; - for (var i = 0, ii = glyphs.length; i < ii; i++) { - var glyph = glyphs[i]; - if (!glyph) - continue; - - var glyphUnicode = glyph.unicode; - if (glyphUnicode in NormalizedUnicodes) - glyphUnicode = NormalizedUnicodes[glyphUnicode]; - result += reverseIfRtl(glyphUnicode); } - return result; + return s; } function adjustWidths(properties) { @@ -19151,6 +20888,29 @@ function adjustWidths(properties) { properties.defaultWidth *= scale; } +var Glyph = (function GlyphClosure() { + function Glyph(fontChar, unicode, accent, width, vmetric, operatorListId) { + this.fontChar = fontChar; + this.unicode = unicode; + this.accent = accent; + this.width = width; + this.vmetric = vmetric; + this.operatorListId = operatorListId; + } + + Glyph.prototype.matchesForCache = + function(fontChar, unicode, accent, width, vmetric, operatorListId) { + return this.fontChar === fontChar && + this.unicode === unicode && + this.accent === accent && + this.width === width && + this.vmetric === vmetric && + this.operatorListId === operatorListId; + }; + + return Glyph; +})(); + /** * 'Font' is the class the outside world should use, it encapsulate all the font * decoding logics whatever type it is (assuming the font type is supported). @@ -19161,13 +20921,15 @@ function adjustWidths(properties) { */ var Font = (function FontClosure() { function Font(name, file, properties) { + var charCode; this.name = name; this.loadedName = properties.loadedName; - this.coded = properties.coded; - this.loadCharProcs = properties.coded; + this.isType3Font = properties.isType3Font; this.sizes = []; + this.glyphCache = {}; + var names = name.split('+'); names = names.length > 1 ? names[1] : names[0]; names = names.split(/[-,_]/g)[0]; @@ -19178,8 +20940,8 @@ var Font = (function FontClosure() { var type = properties.type; this.type = type; - this.fallbackName = this.isMonospace ? 'monospace' : - this.isSerifFont ? 'serif' : 'sans-serif'; + this.fallbackName = (this.isMonospace ? 'monospace' : + (this.isSerifFont ? 'serif' : 'sans-serif')); this.differences = properties.differences; this.widths = properties.widths; @@ -19198,9 +20960,9 @@ var Font = (function FontClosure() { this.toFontChar = []; if (properties.type == 'Type3') { - for (var charCode = 0; charCode < 256; charCode++) { - this.toFontChar[charCode] = this.differences[charCode] || - properties.defaultEncoding[charCode]; + for (charCode = 0; charCode < 256; charCode++) { + this.toFontChar[charCode] = (this.differences[charCode] || + properties.defaultEncoding[charCode]); } return; } @@ -19212,7 +20974,13 @@ var Font = (function FontClosure() { this.defaultVMetrics = properties.defaultVMetrics; } - if (!file) { + if (!file || file.isEmpty) { + if (file) { + // Some bad PDF generators will include empty font files, + // attempting to recover by assuming that no file exists. + warn('Font file is empty in "' + name + '" (' + this.loadedName + ')'); + } + this.missingFile = true; // The file data is not specified. Trying to fix the font name // to be used with the canvas.font. @@ -19221,8 +20989,8 @@ var Font = (function FontClosure() { fontName = stdFontMap[fontName] || nonStdFontMap[fontName] || fontName; this.bold = (fontName.search(/bold/gi) != -1); - this.italic = (fontName.search(/oblique/gi) != -1) || - (fontName.search(/italic/gi) != -1); + this.italic = ((fontName.search(/oblique/gi) != -1) || + (fontName.search(/italic/gi) != -1)); // Use 'name' instead of 'fontName' here because the original // name ArialBlack for example will be replaced by Helvetica. @@ -19242,7 +21010,7 @@ var Font = (function FontClosure() { this.toUnicode = map; } else if (/Symbol/i.test(fontName)) { var symbols = Encodings.SymbolSetEncoding; - for (var charCode in symbols) { + for (charCode in symbols) { var fontChar = GlyphsUnicode[symbols[charCode]]; if (!fontChar) { continue; @@ -19251,13 +21019,13 @@ var Font = (function FontClosure() { } } else if (isStandardFont) { this.toFontChar = []; - for (var charCode in properties.defaultEncoding) { + for (charCode in properties.defaultEncoding) { var glyphName = properties.differences[charCode] || properties.defaultEncoding[charCode]; this.toFontChar[charCode] = GlyphsUnicode[glyphName]; } } else { - for (var charCode in this.toUnicode) { + for (charCode in this.toUnicode) { this.toFontChar[charCode] = this.toUnicode[charCode].charCodeAt(0); } } @@ -19268,10 +21036,17 @@ var Font = (function FontClosure() { // Some fonts might use wrong font types for Type1C or CIDFontType0C var subtype = properties.subtype; - if (subtype == 'Type1C' && (type != 'Type1' && type != 'MMType1')) - type = 'Type1'; - if (subtype == 'CIDFontType0C' && type != 'CIDFontType0') + if (subtype == 'Type1C' && (type != 'Type1' && type != 'MMType1')) { + // Some TrueType fonts by mistake claim Type1C + if (isTrueTypeFile(file)) { + subtype = 'TrueType'; + } else { + type = 'Type1'; + } + } + if (subtype == 'CIDFontType0C' && type != 'CIDFontType0') { type = 'CIDFontType0'; + } // XXX: Temporarily change the type for open type so we trigger a warning. // This should be removed when we add support for open type. if (subtype === 'OpenType') { @@ -19320,29 +21095,19 @@ var Font = (function FontClosure() { this.loading = true; } - function stringToArray(str) { - var array = []; - for (var i = 0, ii = str.length; i < ii; ++i) - array[i] = str.charCodeAt(i); - - return array; - } - - function arrayToString(arr) { - var strBuf = []; - for (var i = 0, ii = arr.length; i < ii; ++i) { - strBuf.push(String.fromCharCode(arr[i])); - } - return strBuf.join(''); - } + Font.getFontID = (function () { + var ID = 1; + return function Font_getFontID() { + return String(ID++); + }; + })(); - function int16(bytes) { - return (bytes[0] << 8) + (bytes[1] & 0xff); + function int16(b0, b1) { + return (b0 << 8) + b1; } - function int32(bytes) { - return (bytes[0] << 24) + (bytes[1] << 16) + - (bytes[2] << 8) + (bytes[3] & 0xff); + function int32(b0, b1, b2, b3) { + return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3; } function getMaxPower2(number) { @@ -19354,34 +21119,27 @@ var Font = (function FontClosure() { } value = 2; - for (var i = 1; i < maxPower; i++) + for (var i = 1; i < maxPower; i++) { value *= 2; + } return value; } function string16(value) { - return String.fromCharCode((value >> 8) & 0xff) + - String.fromCharCode(value & 0xff); + return String.fromCharCode((value >> 8) & 0xff, value & 0xff); } function safeString16(value) { // clamp value to the 16-bit int range - value = value > 0x7FFF ? 0x7FFF : value < -0x8000 ? -0x8000 : value; - return String.fromCharCode((value >> 8) & 0xff) + - String.fromCharCode(value & 0xff); - } - - function string32(value) { - return String.fromCharCode((value >> 24) & 0xff) + - String.fromCharCode((value >> 16) & 0xff) + - String.fromCharCode((value >> 8) & 0xff) + - String.fromCharCode(value & 0xff); + value = (value > 0x7FFF ? 0x7FFF : (value < -0x8000 ? -0x8000 : value)); + return String.fromCharCode((value >> 8) & 0xff, value & 0xff); } function createOpenTypeHeader(sfnt, file, numTables) { // Windows hates the Mac TrueType sfnt version number - if (sfnt == 'true') + if (sfnt == 'true') { sfnt = string32(0x00010000); + } // sfnt version (4 bytes) var header = sfnt; @@ -19412,17 +21170,19 @@ var Font = (function FontClosure() { var length = data.length; // Per spec tables must be 4-bytes align so add padding as needed - while (data.length & 3) + while (data.length & 3) { data.push(0x00); - - while (file.virtualOffset & 3) + } + while (file.virtualOffset & 3) { file.virtualOffset++; + } // checksum var checksum = 0, n = data.length; - for (var i = 0; i < n; i += 4) - checksum = (checksum + int32([data[i], data[i + 1], data[i + 2], - data[i + 3]])) | 0; + for (var i = 0; i < n; i += 4) { + checksum = (checksum + int32(data[i], data[i + 1], data[i + 2], + data[i + 3])) | 0; + } var tableEntry = (tag + string32(checksum) + string32(offset) + string32(length)); @@ -19430,6 +21190,11 @@ var Font = (function FontClosure() { file.virtualOffset += data.length; } + function isTrueTypeFile(file) { + var header = file.peekBytes(4); + return readUint32(header, 0) === 0x00010000; + } + /** * Rebuilds the char code to glyph ID map by trying to replace the char codes * with their unicode value. It also moves char codes that are in known @@ -19446,7 +21211,6 @@ var Font = (function FontClosure() { var isIdentityUnicode = properties.isIdentityUnicode; var newMap = Object.create(null); var toFontChar = []; - var usedCharCodes = []; var usedFontCharCodes = []; var nextAvailableFontCharCode = PRIVATE_USE_OFFSET_START; for (var originalCharCode in charCodeToGlyphId) { @@ -19473,6 +21237,8 @@ var Font = (function FontClosure() { fontCharCode === 0x7F || // Control char fontCharCode === 0xAD || // Soft hyphen (fontCharCode >= 0x80 && fontCharCode <= 0x9F) || // Control chars + // Prevent drawing characters in the specials unicode block. + (fontCharCode >= 0xFFF0 && fontCharCode <= 0xFFFF) || (isSymbolic && isIdentityUnicode)) && nextAvailableFontCharCode <= PRIVATE_USE_OFFSET_END) { // Room left. // Loop to try and find a free spot in the private use area. @@ -19494,7 +21260,8 @@ var Font = (function FontClosure() { } return { toFontChar: toFontChar, - charCodeToGlyphId: newMap + charCodeToGlyphId: newMap, + nextAvailableFontCharCode: nextAvailableFontCharCode }; } @@ -19521,7 +21288,9 @@ var Font = (function FontClosure() { codeIndices.push(codes[n].glyphId); ++end; ++n; - if (end === 0xFFFF) { break; } + if (end === 0xFFFF) { + break; + } } ranges.push([start, end, codeIndices]); } @@ -19538,7 +21307,8 @@ var Font = (function FontClosure() { '\x00\x01' + // encodingID string32(4 + numTables * 8); // start of the table record - for (var i = ranges.length - 1; i >= 0; --i) { + var i, ii, j, jj; + for (i = ranges.length - 1; i >= 0; --i) { if (ranges[i][0] <= 0xFFFF) { break; } } var bmpLength = i + 1; @@ -19561,15 +21331,16 @@ var Font = (function FontClosure() { var glyphsIds = ''; var bias = 0; - for (var i = 0, ii = bmpLength; i < ii; i++) { - var range = ranges[i]; - var start = range[0]; - var end = range[1]; + var range, start, end, codes; + for (i = 0, ii = bmpLength; i < ii; i++) { + range = ranges[i]; + start = range[0]; + end = range[1]; startCount += string16(start); endCount += string16(end); - var codes = range[2]; + codes = range[2]; var contiguous = true; - for (var j = 1, jj = codes.length; j < jj; ++j) { + for (j = 1, jj = codes.length; j < jj; ++j) { if (codes[j] !== codes[j - 1] + 1) { contiguous = false; break; @@ -19582,7 +21353,7 @@ var Font = (function FontClosure() { idDeltas += string16(0); idRangeOffsets += string16(offset); - for (var j = 0, jj = codes.length; j < jj; ++j) { + for (j = 0, jj = codes.length; j < jj; ++j) { glyphsIds += string16(codes[j]); } } else { @@ -19616,14 +21387,14 @@ var Font = (function FontClosure() { string32(4 + numTables * 8 + 4 + format314.length); // start of the table record format31012 = ''; - for (var i = 0, ii = ranges.length; i < ii; i++) { - var range = ranges[i]; - var start = range[0]; - var codes = range[2]; + for (i = 0, ii = ranges.length; i < ii; i++) { + range = ranges[i]; + start = range[0]; + codes = range[2]; var code = codes[0]; - for (var j = 1, jj = codes.length; j < jj; ++j) { + for (j = 1, jj = codes.length; j < jj; ++j) { if (codes[j] !== codes[j - 1] + 1) { - var end = range[0] + j - 1; + end = range[0] + j - 1; format31012 += string32(start) + // startCharCode string32(end) + // endCharCode string32(code); // startGlyphID @@ -19650,21 +21421,21 @@ var Font = (function FontClosure() { function validateOS2Table(os2) { var stream = new Stream(os2.data); - var version = int16(stream.getBytes(2)); + var version = stream.getUint16(); // TODO verify all OS/2 tables fields, but currently we validate only those // that give us issues stream.getBytes(60); // skipping type, misc sizes, panose, unicode ranges - var selection = int16(stream.getBytes(2)); + var selection = stream.getUint16(); if (version < 4 && (selection & 0x0300)) { return false; } - var firstChar = int16(stream.getBytes(2)); - var lastChar = int16(stream.getBytes(2)); + var firstChar = stream.getUint16(); + var lastChar = stream.getUint16(); if (firstChar > lastChar) { return false; } stream.getBytes(6); // skipping sTypoAscender/Descender/LineGap - var usWinAscent = int16(stream.getBytes(2)); + var usWinAscent = stream.getUint16(); if (usWinAscent === 0) { // makes font unreadable by windows return false; } @@ -19694,10 +21465,12 @@ var Font = (function FontClosure() { if (charstrings) { for (var code in charstrings) { code |= 0; - if (firstCharIndex > code || !firstCharIndex) + if (firstCharIndex > code || !firstCharIndex) { firstCharIndex = code; - if (lastCharIndex < code) + } + if (lastCharIndex < code) { lastCharIndex = code; + } var position = getUnicodeRangeFor(code); if (position < 32) { @@ -19719,18 +21492,18 @@ var Font = (function FontClosure() { } var bbox = properties.bbox || [0, 0, 0, 0]; - var unitsPerEm = override.unitsPerEm || - 1 / (properties.fontMatrix || FONT_IDENTITY_MATRIX)[0]; + var unitsPerEm = (override.unitsPerEm || + 1 / (properties.fontMatrix || FONT_IDENTITY_MATRIX)[0]); // if the font units differ to the PDF glyph space units // then scale up the values - var scale = properties.ascentScaled ? 1.0 : - unitsPerEm / PDF_GLYPH_SPACE_UNITS; + var scale = (properties.ascentScaled ? 1.0 : + unitsPerEm / PDF_GLYPH_SPACE_UNITS); - var typoAscent = override.ascent || Math.round(scale * - (properties.ascent || bbox[3])); - var typoDescent = override.descent || Math.round(scale * - (properties.descent || bbox[1])); + var typoAscent = (override.ascent || + Math.round(scale * (properties.ascent || bbox[3]))); + var typoDescent = (override.descent || + Math.round(scale * (properties.descent || bbox[1]))); if (typoDescent > 0 && properties.descent > 0 && bbox[1] < 0) { typoDescent = -typoDescent; // fixing incorrect descent } @@ -19781,15 +21554,15 @@ var Font = (function FontClosure() { function createPostTable(properties) { var angle = Math.floor(properties.italicAngle * (Math.pow(2, 16))); - return '\x00\x03\x00\x00' + // Version number - string32(angle) + // italicAngle - '\x00\x00' + // underlinePosition - '\x00\x00' + // underlineThickness - string32(properties.fixedPitch) + // isFixedPitch - '\x00\x00\x00\x00' + // minMemType42 - '\x00\x00\x00\x00' + // maxMemType42 - '\x00\x00\x00\x00' + // minMemType1 - '\x00\x00\x00\x00'; // maxMemType1 + return ('\x00\x03\x00\x00' + // Version number + string32(angle) + // italicAngle + '\x00\x00' + // underlinePosition + '\x00\x00' + // underlineThickness + string32(properties.fixedPitch) + // isFixedPitch + '\x00\x00\x00\x00' + // minMemType42 + '\x00\x00\x00\x00' + // maxMemType42 + '\x00\x00\x00\x00' + // minMemType1 + '\x00\x00\x00\x00'); // maxMemType1 } function createNameTable(name, proto) { @@ -19813,11 +21586,12 @@ var Font = (function FontClosure() { // Mac want 1-byte per character strings while Windows want // 2-bytes per character, so duplicate the names table var stringsUnicode = []; - for (var i = 0, ii = strings.length; i < ii; i++) { - var str = proto[1][i] || strings[i]; + var i, ii, j, jj, str; + for (i = 0, ii = strings.length; i < ii; i++) { + str = proto[1][i] || strings[i]; var strBufUnicode = []; - for (var j = 0, jj = str.length; j < jj; j++) { + for (j = 0, jj = str.length; j < jj; j++) { strBufUnicode.push(string16(str.charCodeAt(j))); } stringsUnicode.push(strBufUnicode.join('')); @@ -19836,10 +21610,10 @@ var Font = (function FontClosure() { // Build the name records field var strOffset = 0; - for (var i = 0, ii = platforms.length; i < ii; i++) { + for (i = 0, ii = platforms.length; i < ii; i++) { var strs = names[i]; - for (var j = 0, jj = strs.length; j < jj; j++) { - var str = strs[j]; + for (j = 0, jj = strs.length; j < jj; j++) { + str = strs[j]; var nameRecord = platforms[i] + // platform ID encodings[i] + // encoding ID @@ -19869,23 +21643,20 @@ var Font = (function FontClosure() { exportData: function Font_exportData() { var data = {}; for (var i in this) { - if (this.hasOwnProperty(i)) + if (this.hasOwnProperty(i)) { data[i] = this[i]; + } } return data; }, checkAndRepair: function Font_checkAndRepair(name, font, properties) { function readTableEntry(file) { - var tag = file.getBytes(4); - tag = String.fromCharCode(tag[0]) + - String.fromCharCode(tag[1]) + - String.fromCharCode(tag[2]) + - String.fromCharCode(tag[3]); + var tag = bytesToString(file.getBytes(4)); - var checksum = int32(file.getBytes(4)); - var offset = int32(file.getBytes(4)); - var length = int32(file.getBytes(4)); + var checksum = file.getInt32(); + var offset = file.getInt32() >>> 0; + var length = file.getInt32() >>> 0; // Read the table associated data var previousPosition = file.pos; @@ -19911,11 +21682,11 @@ var Font = (function FontClosure() { function readOpenTypeHeader(ttf) { return { - version: arrayToString(ttf.getBytes(4)), - numTables: int16(ttf.getBytes(2)), - searchRange: int16(ttf.getBytes(2)), - entrySelector: int16(ttf.getBytes(2)), - rangeShift: int16(ttf.getBytes(2)) + version: bytesToString(ttf.getBytes(4)), + numTables: ttf.getUint16(), + searchRange: ttf.getUint16(), + entrySelector: ttf.getUint16(), + rangeShift: ttf.getUint16() }; } @@ -19924,11 +21695,12 @@ var Font = (function FontClosure() { * PDF spec */ function readCmapTable(cmap, font, isSymbolicFont) { + var segment; var start = (font.start ? font.start : 0) + cmap.offset; font.pos = start; - var version = int16(font.getBytes(2)); - var numTables = int16(font.getBytes(2)); + var version = font.getUint16(); + var numTables = font.getUint16(); var potentialTable; var canBreak = false; @@ -19939,9 +21711,9 @@ var Font = (function FontClosure() { // The following takes advantage of the fact that the tables are sorted // to work. for (var i = 0; i < numTables; i++) { - var platformId = int16(font.getBytes(2)); - var encodingId = int16(font.getBytes(2)); - var offset = int32(font.getBytes(4)); + var platformId = font.getUint16(); + var encodingId = font.getUint16(); + var offset = font.getInt32() >>> 0; var useTable = false; if (platformId == 1 && encodingId === 0) { @@ -19974,16 +21746,17 @@ var Font = (function FontClosure() { } font.pos = start + potentialTable.offset; - var format = int16(font.getBytes(2)); - var length = int16(font.getBytes(2)); - var language = int16(font.getBytes(2)); + var format = font.getUint16(); + var length = font.getUint16(); + var language = font.getUint16(); var hasShortCmap = false; var mappings = []; + var j, glyphId; // TODO(mack): refactor this cmap subtable reading logic out if (format === 0) { - for (var j = 0; j < 256; j++) { + for (j = 0; j < 256; j++) { var index = font.getByte(); if (!index) { continue; @@ -19997,25 +21770,25 @@ var Font = (function FontClosure() { } else if (format === 4) { // re-creating the table in format 4 since the encoding // might be changed - var segCount = (int16(font.getBytes(2)) >> 1); + var segCount = (font.getUint16() >> 1); font.getBytes(6); // skipping range fields var segIndex, segments = []; for (segIndex = 0; segIndex < segCount; segIndex++) { - segments.push({ end: int16(font.getBytes(2)) }); + segments.push({ end: font.getUint16() }); } - font.getBytes(2); + font.getUint16(); for (segIndex = 0; segIndex < segCount; segIndex++) { - segments[segIndex].start = int16(font.getBytes(2)); + segments[segIndex].start = font.getUint16(); } for (segIndex = 0; segIndex < segCount; segIndex++) { - segments[segIndex].delta = int16(font.getBytes(2)); + segments[segIndex].delta = font.getUint16(); } var offsetsCount = 0; for (segIndex = 0; segIndex < segCount; segIndex++) { - var segment = segments[segIndex]; - var rangeOffset = int16(font.getBytes(2)); + segment = segments[segIndex]; + var rangeOffset = font.getUint16(); if (!rangeOffset) { segment.offsetIndex = -1; continue; @@ -20024,26 +21797,28 @@ var Font = (function FontClosure() { var offsetIndex = (rangeOffset >> 1) - (segCount - segIndex); segment.offsetIndex = offsetIndex; offsetsCount = Math.max(offsetsCount, offsetIndex + - segment.end - segment.start + 1); + segment.end - segment.start + 1); } var offsets = []; - for (var j = 0; j < offsetsCount; j++) { - offsets.push(int16(font.getBytes(2))); + for (j = 0; j < offsetsCount; j++) { + offsets.push(font.getUint16()); } for (segIndex = 0; segIndex < segCount; segIndex++) { - var segment = segments[segIndex]; - var start = segment.start, end = segment.end; - var delta = segment.delta, offsetIndex = segment.offsetIndex; + segment = segments[segIndex]; + start = segment.start; + var end = segment.end; + var delta = segment.delta; + offsetIndex = segment.offsetIndex; - for (var j = start; j <= end; j++) { + for (j = start; j <= end; j++) { if (j == 0xFFFF) { continue; } - var glyphId = offsetIndex < 0 ? j : - offsets[offsetIndex + j - start]; + glyphId = (offsetIndex < 0 ? + j : offsets[offsetIndex + j - start]); glyphId = (glyphId + delta) & 0xFFFF; if (glyphId === 0) { continue; @@ -20060,13 +21835,11 @@ var Font = (function FontClosure() { // table. (This looks weird, so I can have missed something), this // works on Linux but seems to fails on Mac so let's rewrite the // cmap table to a 3-1-4 style - var firstCode = int16(font.getBytes(2)); - var entryCount = int16(font.getBytes(2)); + var firstCode = font.getUint16(); + var entryCount = font.getUint16(); - var glyphs = []; - var ids = []; - for (var j = 0; j < entryCount; j++) { - var glyphId = int16(font.getBytes(2)); + for (j = 0; j < entryCount; j++) { + glyphId = font.getUint16(); var charCode = firstCode + j; mappings.push({ @@ -20082,7 +21855,7 @@ var Font = (function FontClosure() { mappings.sort(function (a, b) { return a.charCode - b.charCode; }); - for (var i = 1; i < mappings.length; i++) { + for (i = 1; i < mappings.length; i++) { if (mappings[i - 1].charCode === mappings[i].charCode) { mappings.splice(i, 1); i--; @@ -20107,7 +21880,7 @@ var Font = (function FontClosure() { font.pos = (font.start ? font.start : 0) + header.offset; font.pos += header.length - 2; - var numOfMetrics = int16(font.getBytes(2)); + var numOfMetrics = font.getUint16(); if (numOfMetrics > numGlyphs) { info('The numOfMetrics (' + numOfMetrics + ') should not be ' + @@ -20122,13 +21895,16 @@ var Font = (function FontClosure() { var numMissing = numOfSidebearings - ((metrics.length - numOfMetrics * 4) >> 1); + var i, ii; if (numMissing > 0) { font.pos = (font.start ? font.start : 0) + metrics.offset; var entries = ''; - for (var i = 0, ii = metrics.length; i < ii; i++) + for (i = 0, ii = metrics.length; i < ii; i++) { entries += String.fromCharCode(font.getByte()); - for (var i = 0; i < numMissing; i++) + } + for (i = 0; i < numMissing; i++) { entries += '\x00\x00'; + } metrics.data = stringToArray(entries); } } @@ -20147,8 +21923,8 @@ var Font = (function FontClosure() { return glyf.length; } - var j = 10, flagsCount = 0; - for (var i = 0; i < contoursCount; i++) { + var i, j = 10, flagsCount = 0; + for (i = 0; i < contoursCount; i++) { var endPoint = (glyf[j] << 8) | glyf[j + 1]; flagsCount = endPoint + 1; j += 2; @@ -20160,7 +21936,7 @@ var Font = (function FontClosure() { var instructionsEnd = j; // validating flags var coordinatesLength = 0; - for (var i = 0; i < flagsCount; i++) { + for (i = 0; i < flagsCount; i++) { var flag = glyf[j++]; if (flag & 0xC0) { // reserved flags must be zero, cleaning up @@ -20211,7 +21987,7 @@ var Font = (function FontClosure() { // Validate version: // Should always be 0x00010000 - var version = int32([data[0], data[1], data[2], data[3]]); + var version = int32(data[0], data[1], data[2], data[3]); if (version >> 16 !== 1) { info('Attempting to fix invalid version in head table: ' + version); data[0] = 0; @@ -20220,7 +21996,7 @@ var Font = (function FontClosure() { data[3] = 0; } - var indexToLocFormat = int16([data[50], data[51]]); + var indexToLocFormat = int16(data[50], data[51]); if (indexToLocFormat < 0 || indexToLocFormat > 1) { info('Attempting to fix invalid indexToLocFormat in head table: ' + indexToLocFormat); @@ -20291,7 +22067,8 @@ var Font = (function FontClosure() { var startOffset = itemDecode(locaData, 0); var writeOffset = 0; itemEncode(locaData, 0, writeOffset); - for (var i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) { + var i, j; + for (i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) { var endOffset = itemDecode(locaData, j); if (endOffset > oldGlyfDataLength && ((oldGlyfDataLength + 3) & ~3) === endOffset) { @@ -20318,8 +22095,9 @@ var Font = (function FontClosure() { // to have single glyph with one point var simpleGlyph = new Uint8Array( [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 0]); - for (var i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) + for (i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) { itemEncode(locaData, j, simpleGlyph.length); + } glyf.data = simpleGlyph; return; } @@ -20345,25 +22123,27 @@ var Font = (function FontClosure() { font.pos = start; var length = post.length, end = start + length; - var version = int32(font.getBytes(4)); + var version = font.getInt32(); // skip rest to the tables font.getBytes(28); var glyphNames; var valid = true; + var i; + switch (version) { case 0x00010000: glyphNames = MacStandardGlyphOrdering; break; case 0x00020000: - var numGlyphs = int16(font.getBytes(2)); + var numGlyphs = font.getUint16(); if (numGlyphs != maxpNumGlyphs) { valid = false; break; } var glyphNameIndexes = []; - for (var i = 0; i < numGlyphs; ++i) { - var index = int16(font.getBytes(2)); + for (i = 0; i < numGlyphs; ++i) { + var index = font.getUint16(); if (index >= 32768) { valid = false; break; @@ -20377,13 +22157,13 @@ var Font = (function FontClosure() { while (font.pos < end) { var stringLength = font.getByte(); var string = ''; - for (var i = 0; i < stringLength; ++i) { + for (i = 0; i < stringLength; ++i) { string += String.fromCharCode(font.getByte()); } customNames.push(string); } glyphNames = []; - for (var i = 0; i < numGlyphs; ++i) { + for (i = 0; i < numGlyphs; ++i) { var j = glyphNameIndexes[i]; if (j < 258) { glyphNames.push(MacStandardGlyphOrdering[j]); @@ -20409,25 +22189,27 @@ var Font = (function FontClosure() { var names = [[], []]; var length = nameTable.length, end = start + length; - var format = int16(font.getBytes(2)); + var format = font.getUint16(); var FORMAT_0_HEADER_LENGTH = 6; if (format !== 0 || length < FORMAT_0_HEADER_LENGTH) { // unsupported name table format or table "too" small return names; } - var numRecords = int16(font.getBytes(2)); - var stringsStart = int16(font.getBytes(2)); + var numRecords = font.getUint16(); + var stringsStart = font.getUint16(); var records = []; var NAME_RECORD_LENGTH = 12; - for (var i = 0; i < numRecords && + var i, ii; + + for (i = 0; i < numRecords && font.pos + NAME_RECORD_LENGTH <= end; i++) { var r = { - platform: int16(font.getBytes(2)), - encoding: int16(font.getBytes(2)), - language: int16(font.getBytes(2)), - name: int16(font.getBytes(2)), - length: int16(font.getBytes(2)), - offset: int16(font.getBytes(2)) + platform: font.getUint16(), + encoding: font.getUint16(), + language: font.getUint16(), + name: font.getUint16(), + length: font.getUint16(), + offset: font.getUint16() }; // using only Macintosh and Windows platform/encoding names if ((r.platform == 1 && r.encoding === 0 && r.language === 0) || @@ -20435,7 +22217,7 @@ var Font = (function FontClosure() { records.push(r); } } - for (var i = 0, ii = records.length; i < ii; i++) { + for (i = 0, ii = records.length; i < ii; i++) { var record = records[i]; var pos = start + stringsStart + record.offset; if (pos + record.length > end) { @@ -20443,12 +22225,11 @@ var Font = (function FontClosure() { } font.pos = pos; var nameIndex = record.name; - var encoding = record.encoding ? 1 : 0; if (record.encoding) { // unicode var str = ''; for (var j = 0, jj = record.length; j < jj; j += 2) { - str += String.fromCharCode(int16(font.getBytes(2))); + str += String.fromCharCode(font.getUint16()); } names[1][nameIndex] = str; } else { @@ -20472,7 +22253,7 @@ var Font = (function FontClosure() { function sanitizeTTProgram(table, ttContext) { var data = table.data; - var i = 0, n, lastEndf = 0, lastDeff = 0; + var i = 0, j, n, b, funcId, pc, lastEndf = 0, lastDeff = 0; var stack = []; var callstack = []; var functionsCalled = []; @@ -20488,7 +22269,7 @@ var Font = (function FontClosure() { if (inFDEF || inELSE) { i += n; } else { - for (var j = 0; j < n; j++) { + for (j = 0; j < n; j++) { stack.push(data[i++]); } } @@ -20497,8 +22278,8 @@ var Font = (function FontClosure() { if (inFDEF || inELSE) { i += n * 2; } else { - for (var j = 0; j < n; j++) { - var b = data[i++]; + for (j = 0; j < n; j++) { + b = data[i++]; stack.push((b << 8) | data[i++]); } } @@ -20507,7 +22288,7 @@ var Font = (function FontClosure() { if (inFDEF || inELSE) { i += n; } else { - for (var j = 0; j < n; j++) { + for (j = 0; j < n; j++) { stack.push(data[i++]); } } @@ -20516,15 +22297,15 @@ var Font = (function FontClosure() { if (inFDEF || inELSE) { i += n * 2; } else { - for (var j = 0; j < n; j++) { - var b = data[i++]; + for (j = 0; j < n; j++) { + b = data[i++]; stack.push((b << 8) | data[i++]); } } } else if (op === 0x2B && !tooComplexToFollowFunctions) { // CALL if (!inFDEF && !inELSE) { // collecting inforamtion about which functions are used - var funcId = stack[stack.length - 1]; + funcId = stack[stack.length - 1]; ttContext.functionsUsed[funcId] = true; if (funcId in ttContext.functionsStackDeltas) { stack.length += ttContext.functionsStackDeltas[funcId]; @@ -20532,7 +22313,7 @@ var Font = (function FontClosure() { functionsCalled.indexOf(funcId) < 0) { callstack.push({data: data, i: i, stackTop: stack.length - 1}); functionsCalled.push(funcId); - var pc = ttContext.functionsDefined[funcId]; + pc = ttContext.functionsDefined[funcId]; if (!pc) { warn('TT: CALL non-existent function'); ttContext.hintsValid = false; @@ -20550,20 +22331,20 @@ var Font = (function FontClosure() { inFDEF = true; // collecting inforamtion about which functions are defined lastDeff = i; - var funcId = stack.pop(); + funcId = stack.pop(); ttContext.functionsDefined[funcId] = {data: data, i: i}; } else if (op === 0x2D) { // ENDF - end of function if (inFDEF) { inFDEF = false; lastEndf = i; } else { - var pc = callstack.pop(); + pc = callstack.pop(); if (!pc) { warn('TT: ENDF bad stack'); ttContext.hintsValid = false; return; } - var funcId = functionsCalled.pop(); + funcId = functionsCalled.pop(); data = pc.data; i = pc.i; ttContext.functionsStackDeltas[funcId] = @@ -20590,7 +22371,9 @@ var Font = (function FontClosure() { if (!inFDEF && !inELSE) { var offset = stack[stack.length - 1]; // only jumping forward to prevent infinite loop - if (offset > 0) { i += offset - 1; } + if (offset > 0) { + i += offset - 1; + } } } // Adjusting stack not extactly, but just enough to get function id @@ -20654,13 +22437,14 @@ var Font = (function FontClosure() { if (content.length > 1) { // concatenating the content items var newLength = 0; - for (var j = 0, jj = content.length; j < jj; j++) { + var j, jj; + for (j = 0, jj = content.length; j < jj; j++) { newLength += content[j].length; } newLength = (newLength + 3) & ~3; var result = new Uint8Array(newLength); var pos = 0; - for (var j = 0, jj = content.length; j < jj; j++) { + for (j = 0, jj = content.length; j < jj; j++) { result.set(content[j], pos); pos += content[j].length; } @@ -20702,11 +22486,13 @@ var Font = (function FontClosure() { var header = readOpenTypeHeader(font); var numTables = header.numTables; + var cff, cffFile; var tables = { 'OS/2': null, cmap: null, head: null, hhea: null, - hmtx: null, maxp: null, name: null, post: null}; + hmtx: null, maxp: null, name: null, post: null }; + var table, tableData; for (var i = 0; i < numTables; i++) { - var table = readTableEntry(font); + table = readTableEntry(font); if (VALID_TABLES.indexOf(table.tag) < 0) { continue; // skipping table if it's not a required or optional table } @@ -20721,8 +22507,8 @@ var Font = (function FontClosure() { // OpenType font if (!tables.head || !tables.hhea || !tables.maxp || !tables.post) { // no major tables: throwing everything at CFFFont - var cffFile = new Stream(tables['CFF '].data); - var cff = new CFFFont(cffFile, properties); + cffFile = new Stream(tables['CFF '].data); + cff = new CFFFont(cffFile, properties); return this.convert(name, cff, properties); } @@ -20743,19 +22529,19 @@ var Font = (function FontClosure() { } font.pos = (font.start || 0) + tables.maxp.offset; - var version = int32(font.getBytes(4)); - var numGlyphs = int16(font.getBytes(2)); + var version = font.getInt32(); + var numGlyphs = font.getUint16(); var maxFunctionDefs = 0; if (version >= 0x00010000 && tables.maxp.length >= 22) { // maxZones can be invalid font.pos += 8; - var maxZones = int16(font.getBytes(2)); + var maxZones = font.getUint16(); if (maxZones > 2) { // reset to 2 if font has invalid maxZones tables.maxp.data[14] = 0; tables.maxp.data[15] = 2; } font.pos += 4; - maxFunctionDefs = int16(font.getBytes(2)); + maxFunctionDefs = font.getUint16(); } var dupFirstEntry = false; @@ -20805,8 +22591,8 @@ var Font = (function FontClosure() { sanitizeHead(tables.head, numGlyphs, isTrueType ? tables.loca.length : 0); if (isTrueType) { - var isGlyphLocationsLong = int16([tables.head.data[50], - tables.head.data[51]]); + var isGlyphLocationsLong = int16(tables.head.data[50], + tables.head.data[51]); sanitizeGlyphLocations(tables.loca, tables.glyf, numGlyphs, isGlyphLocationsLong, hintsValid, dupFirstEntry); } @@ -20830,11 +22616,11 @@ var Font = (function FontClosure() { } } - var charCodeToGlyphId = []; + var charCodeToGlyphId = [], charCode; if (properties.type == 'CIDFontType2') { var cidToGidMap = properties.cidToGidMap || []; var cMap = properties.cMap.map; - for (var charCode in cMap) { + for (charCode in cMap) { charCode |= 0; var cid = cMap[charCode]; assert(cid.length === 1, 'Max size of CID is 65,535'); @@ -20874,7 +22660,7 @@ var Font = (function FontClosure() { properties.baseEncodingName === 'WinAnsiEncoding') { baseEncoding = Encodings[properties.baseEncodingName]; } - for (var charCode = 0; charCode < 256; charCode++) { + for (charCode = 0; charCode < 256; charCode++) { var glyphName; if (this.differences && charCode in this.differences) { glyphName = this.differences[charCode]; @@ -20896,7 +22682,7 @@ var Font = (function FontClosure() { } var found = false; - for (var i = 0; i < cmapMappingsLength; ++i) { + for (i = 0; i < cmapMappingsLength; ++i) { if (cmapMappings[i].charCode === unicodeOrCharCode) { charCodeToGlyphId[charCode] = cmapMappings[i].glyphId; found = true; @@ -20906,7 +22692,7 @@ var Font = (function FontClosure() { if (!found && properties.glyphNames) { // Try to map using the post table. There are currently no known // pdfs that this fixes. - var glyphId = properties.glyphNames.indexOf(glyphName); + glyphId = properties.glyphNames.indexOf(glyphName); if (glyphId > 0) { charCodeToGlyphId[charCode] = glyphId; } @@ -20926,8 +22712,8 @@ var Font = (function FontClosure() { // associated glyph descriptions from the subtable'. This means // charcodes in the cmap will be single bytes, so no-op since // glyph.charCode & 0xFF === glyph.charCode - for (var i = 0; i < cmapMappingsLength; ++i) { - var charCode = cmapMappings[i].charCode & 0xFF; + for (i = 0; i < cmapMappingsLength; ++i) { + charCode = cmapMappings[i].charCode & 0xFF; charCodeToGlyphId[charCode] = cmapMappings[i].glyphId; } } @@ -20950,11 +22736,11 @@ var Font = (function FontClosure() { // extract some more font properties from the OpenType head and // hhea tables; yMin and descent value are always negative var override = { - unitsPerEm: int16([tables.head.data[18], tables.head.data[19]]), - yMax: int16([tables.head.data[42], tables.head.data[43]]), - yMin: int16([tables.head.data[38], tables.head.data[39]]) - 0x10000, - ascent: int16([tables.hhea.data[4], tables.hhea.data[5]]), - descent: int16([tables.hhea.data[6], tables.hhea.data[7]]) - 0x10000 + unitsPerEm: int16(tables.head.data[18], tables.head.data[19]), + yMax: int16(tables.head.data[42], tables.head.data[43]), + yMin: int16(tables.head.data[38], tables.head.data[39]) - 0x10000, + ascent: int16(tables.hhea.data[4], tables.hhea.data[5]), + descent: int16(tables.hhea.data[6], tables.hhea.data[7]) - 0x10000 }; tables['OS/2'] = { @@ -20976,9 +22762,9 @@ var Font = (function FontClosure() { if (!isTrueType) { try { // Trying to repair CFF file - var cffFile = new Stream(tables['CFF '].data); + cffFile = new Stream(tables['CFF '].data); var parser = new CFFParser(cffFile, properties); - var cff = parser.parse(); + cff = parser.parse(); var compiler = new CFFCompiler(cff); tables['CFF '].data = compiler.compile(); } catch (e) { @@ -20999,25 +22785,27 @@ var Font = (function FontClosure() { } // rewrite the tables but tweak offsets - for (var i = 0; i < numTables; i++) { - var table = tables[tablesNames[i]]; + for (i = 0; i < numTables; i++) { + table = tables[tablesNames[i]]; var data = []; - var tableData = table.data; - for (var j = 0, jj = tableData.length; j < jj; j++) + tableData = table.data; + for (var j = 0, jj = tableData.length; j < jj; j++) { data.push(tableData[j]); + } createTableEntry(ttf, table.tag, data); } // Add the table datas - for (var i = 0; i < numTables; i++) { - var table = tables[tablesNames[i]]; - var tableData = table.data; - ttf.file += arrayToString(tableData); + for (i = 0; i < numTables; i++) { + table = tables[tablesNames[i]]; + tableData = table.data; + ttf.file += bytesToString(new Uint8Array(tableData)); // 4-byte aligned data - while (ttf.file.length & 3) + while (ttf.file.length & 3) { ttf.file += String.fromCharCode(0); + } } return stringToArray(ttf.file); @@ -21028,8 +22816,6 @@ var Font = (function FontClosure() { // to write the table entry information about a table and another offset // representing the offset where to draw the actual data of a particular // table - var REQ_TABLES_CNT = 9; - var otf = { file: '', virtualOffset: 9 * (4 * 4) @@ -21045,18 +22831,25 @@ var Font = (function FontClosure() { this.toFontChar = newMapping.toFontChar; var numGlyphs = font.numGlyphs; + function getCharCode(charCodeToGlyphId, glyphId, addMap) { + for (var charCode in charCodeToGlyphId) { + if (glyphId === charCodeToGlyphId[charCode]) { + return charCode | 0; + } + } + if (addMap) { + newMapping.charCodeToGlyphId[newMapping.nextAvailableFontCharCode] = + glyphId; + return newMapping.nextAvailableFontCharCode++; + } + return null; + } + var seacs = font.seacs; if (SEAC_ANALYSIS_ENABLED && seacs && seacs.length) { var matrix = properties.fontMatrix || FONT_IDENTITY_MATRIX; var charset = font.getCharset(); - var charCodeToGlyphId = mapping; - var toFontChar = newMapping.toFontChar; - var seacs = font.seacs; var seacMap = Object.create(null); - var glyphIdToCharCode = Object.create(null); - for (var charCode in charCodeToGlyphId) { - glyphIdToCharCode[charCodeToGlyphId[charCode]] = charCode | 0; - } for (var glyphId in seacs) { glyphId |= 0; var seac = seacs[glyphId]; @@ -21071,10 +22864,23 @@ var Font = (function FontClosure() { x: seac[0] * matrix[0] + seac[1] * matrix[2] + matrix[4], y: seac[0] * matrix[1] + seac[1] * matrix[3] + matrix[5] }; - var charCode = glyphIdToCharCode[glyphId]; + + var charCode = getCharCode(mapping, glyphId); + if (charCode === null) { + // There's no point in mapping it if the char code was never mapped + // to begin with. + continue; + } + // Find a fontCharCode that maps to the base and accent glyphs. If one + // doesn't exists, create it. + var charCodeToGlyphId = newMapping.charCodeToGlyphId; + var baseFontCharCode = getCharCode(charCodeToGlyphId, baseGlyphId, + true); + var accentFontCharCode = getCharCode(charCodeToGlyphId, accentGlyphId, + true); seacMap[charCode] = { - baseFontCharCode: toFontChar[glyphIdToCharCode[baseGlyphId]], - accentFontCharCode: toFontChar[glyphIdToCharCode[accentGlyphId]], + baseFontCharCode: baseFontCharCode, + accentFontCharCode: accentFontCharCode, accentOffset: accentOffset }; } @@ -21097,56 +22903,56 @@ var Font = (function FontClosure() { // Font header 'head': (function fontFieldsHead() { return stringToArray( - '\x00\x01\x00\x00' + // Version number - '\x00\x00\x10\x00' + // fontRevision - '\x00\x00\x00\x00' + // checksumAdjustement - '\x5F\x0F\x3C\xF5' + // magicNumber - '\x00\x00' + // Flags - safeString16(unitsPerEm) + // unitsPerEM - '\x00\x00\x00\x00\x9e\x0b\x7e\x27' + // creation date - '\x00\x00\x00\x00\x9e\x0b\x7e\x27' + // modifification date - '\x00\x00' + // xMin - safeString16(properties.descent) + // yMin - '\x0F\xFF' + // xMax - safeString16(properties.ascent) + // yMax - string16(properties.italicAngle ? 2 : 0) + // macStyle - '\x00\x11' + // lowestRecPPEM - '\x00\x00' + // fontDirectionHint - '\x00\x00' + // indexToLocFormat - '\x00\x00'); // glyphDataFormat + '\x00\x01\x00\x00' + // Version number + '\x00\x00\x10\x00' + // fontRevision + '\x00\x00\x00\x00' + // checksumAdjustement + '\x5F\x0F\x3C\xF5' + // magicNumber + '\x00\x00' + // Flags + safeString16(unitsPerEm) + // unitsPerEM + '\x00\x00\x00\x00\x9e\x0b\x7e\x27' + // creation date + '\x00\x00\x00\x00\x9e\x0b\x7e\x27' + // modifification date + '\x00\x00' + // xMin + safeString16(properties.descent) + // yMin + '\x0F\xFF' + // xMax + safeString16(properties.ascent) + // yMax + string16(properties.italicAngle ? 2 : 0) + // macStyle + '\x00\x11' + // lowestRecPPEM + '\x00\x00' + // fontDirectionHint + '\x00\x00' + // indexToLocFormat + '\x00\x00'); // glyphDataFormat })(), // Horizontal header 'hhea': (function fontFieldsHhea() { return stringToArray( - '\x00\x01\x00\x00' + // Version number - safeString16(properties.ascent) + // Typographic Ascent - safeString16(properties.descent) + // Typographic Descent - '\x00\x00' + // Line Gap - '\xFF\xFF' + // advanceWidthMax - '\x00\x00' + // minLeftSidebearing - '\x00\x00' + // minRightSidebearing - '\x00\x00' + // xMaxExtent - safeString16(properties.capHeight) + // caretSlopeRise - safeString16(Math.tan(properties.italicAngle) * - properties.xHeight) + // caretSlopeRun - '\x00\x00' + // caretOffset - '\x00\x00' + // -reserved- - '\x00\x00' + // -reserved- - '\x00\x00' + // -reserved- - '\x00\x00' + // -reserved- - '\x00\x00' + // metricDataFormat - string16(numGlyphs + 1)); // Number of HMetrics + '\x00\x01\x00\x00' + // Version number + safeString16(properties.ascent) + // Typographic Ascent + safeString16(properties.descent) + // Typographic Descent + '\x00\x00' + // Line Gap + '\xFF\xFF' + // advanceWidthMax + '\x00\x00' + // minLeftSidebearing + '\x00\x00' + // minRightSidebearing + '\x00\x00' + // xMaxExtent + safeString16(properties.capHeight) + // caretSlopeRise + safeString16(Math.tan(properties.italicAngle) * + properties.xHeight) + // caretSlopeRun + '\x00\x00' + // caretOffset + '\x00\x00' + // -reserved- + '\x00\x00' + // -reserved- + '\x00\x00' + // -reserved- + '\x00\x00' + // -reserved- + '\x00\x00' + // metricDataFormat + string16(numGlyphs)); // Number of HMetrics })(), // Horizontal metrics 'hmtx': (function fontFieldsHmtx() { var charstrings = font.charstrings; var hmtx = '\x00\x00\x00\x00'; // Fake .notdef - for (var i = 0, ii = numGlyphs; i < ii; i++) { + for (var i = 1, ii = numGlyphs; i < ii; i++) { // TODO: For CFF fonts the width should technically match th x in // the glyph, but it doesn't seem to matter. - var charstring = charstrings ? charstrings[i] : {}; + var charstring = charstrings ? charstrings[i - 1] : {}; var width = 'width' in charstring ? charstring.width : 0; hmtx += string16(width) + string16(0); } @@ -21156,8 +22962,8 @@ var Font = (function FontClosure() { // Maximum profile 'maxp': (function fontFieldsMaxp() { return stringToArray( - '\x00\x00\x50\x00' + // Version number - string16(numGlyphs + 1)); // Num of glyphs + '\x00\x00\x50\x00' + // Version number + string16(numGlyphs)); // Num of glyphs })(), // Naming tables @@ -21167,12 +22973,13 @@ var Font = (function FontClosure() { 'post': stringToArray(createPostTable(properties)) }; - for (var field in fields) + var field; + for (field in fields) { createTableEntry(otf, field, fields[field]); - - for (var field in fields) { + } + for (field in fields) { var table = fields[field]; - otf.file += arrayToString(table); + otf.file += bytesToString(new Uint8Array(table)); } return stringToArray(otf.file); @@ -21191,7 +22998,7 @@ var Font = (function FontClosure() { toUnicode: null }; // Section 9.10.2 Mapping Character Codes to Unicode Values - if (properties.toUnicode) { + if (properties.toUnicode && properties.toUnicode.length !== 0) { map.toUnicode = properties.toUnicode; return map; } @@ -21200,20 +23007,36 @@ var Font = (function FontClosure() { // the differences array only contains adobe standard or symbol set names, // in pratice it seems better to always try to create a toUnicode // map based of the default encoding. + var toUnicode, charcode; if (!properties.composite /* is simple font */) { - var toUnicode = []; + toUnicode = []; var encoding = properties.defaultEncoding.slice(); // Merge in the differences array. var differences = properties.differences; - for (var charcode in differences) { + for (charcode in differences) { encoding[charcode] = differences[charcode]; } - for (var charcode in encoding) { + for (charcode in encoding) { // a) Map the character code to a character name. var glyphName = encoding[charcode]; // b) Look up the character name in the Adobe Glyph List (see the // Bibliography) to obtain the corresponding Unicode value. if (glyphName === '' || !(glyphName in GlyphsUnicode)) { + // (undocumented) c) Few heuristics to recognize unknown glyphs + // NOTE: Adobe Reader does not do this step, but OSX Preview does + var code; + // Gxx glyph + if (glyphName.length === 3 && + glyphName[0] === 'G' && + (code = parseInt(glyphName.substr(1), 16))) { + toUnicode[charcode] = String.fromCharCode(code); + } + // Cddd glyph + if (glyphName.length >= 3 && + glyphName[0] === 'C' && + (code = +glyphName.substr(1))) { + toUnicode[charcode] = String.fromCharCode(code); + } continue; } toUnicode[charcode] = String.fromCharCode(GlyphsUnicode[glyphName]); @@ -21247,10 +23070,11 @@ var Font = (function FontClosure() { var ucs2CMapName = new Name(registry + '-' + ordering + '-UCS2'); // d) Obtain the CMap with the name constructed in step (c) (available // from the ASN Web site; see the Bibliography). - var ucs2CMap = CMapFactory.create(ucs2CMapName, PDFJS.cMapUrl, null); + var ucs2CMap = CMapFactory.create(ucs2CMapName, + { url: PDFJS.cMapUrl, packed: PDFJS.cMapPacked }, null); var cMap = properties.cMap; - var toUnicode = []; - for (var charcode in cMap.map) { + toUnicode = []; + for (charcode in cMap.map) { var cid = cMap.map[charcode]; assert(cid.length === 1, 'Max size of CID is 65,535'); // e) Map the CID obtained in step (a) according to the CMap obtained @@ -21267,9 +23091,9 @@ var Font = (function FontClosure() { } // The viewer's choice, just use an identity map. - var toUnicode = []; + toUnicode = []; var firstChar = properties.firstChar, lastChar = properties.lastChar; - for (var i = firstChar, ii = lastChar; i <= ii; i++) { + for (var i = firstChar; i <= lastChar; i++) { toUnicode[i] = String.fromCharCode(i); } map.isIdentity = true; @@ -21301,15 +23125,18 @@ var Font = (function FontClosure() { } } // ... via toUnicode map - if (!charcode && 'toUnicode' in this) + if (!charcode && 'toUnicode' in this) { charcode = this.toUnicode.indexOf(glyphUnicode); + } // setting it to unicode if negative or undefined - if (charcode <= 0) + if (charcode <= 0) { charcode = glyphUnicode; + } // trying to get width via charcode width = this.widths[charcode]; - if (width) + if (width) { break; // the non-zero width found + } } width = width || this.defaultWidth; // Do not shadow the property here. See discussion: @@ -21319,19 +23146,19 @@ var Font = (function FontClosure() { }, charToGlyph: function Font_charToGlyph(charcode) { - var fontCharCode, width, operatorList; + var fontCharCode, width, operatorListId; var widthCode = charcode; if (this.cMap && charcode in this.cMap.map) { widthCode = this.cMap.map[charcode].charCodeAt(0); } - var width = this.widths[widthCode]; + width = this.widths[widthCode]; width = isNum(width) ? width : this.defaultWidth; var vmetric = this.vmetrics && this.vmetrics[widthCode]; - var unicodeChars = this.toUnicode[charcode] || charcode; - if (typeof unicodeChars === 'number') { - unicodeChars = String.fromCharCode(unicodeChars); + var unicode = this.toUnicode[charcode] || charcode; + if (typeof unicode === 'number') { + unicode = String.fromCharCode(unicode); } // First try the toFontChar map, if it's not there then try falling @@ -21341,9 +23168,9 @@ var Font = (function FontClosure() { fontCharCode = mapSpecialUnicodeValues(fontCharCode); } - if (this.type === 'Type3') { + if (this.isType3Font) { // Font char code in this case is actually a glyph name. - operatorList = this.charProcOperatorList[fontCharCode]; + operatorListId = fontCharCode; } var accent = null; @@ -21356,44 +23183,49 @@ var Font = (function FontClosure() { }; } - return { - fontChar: String.fromCharCode(fontCharCode), - unicode: unicodeChars, - accent: accent, - width: width, - vmetric: vmetric, - operatorList: operatorList - }; + var fontChar = String.fromCharCode(fontCharCode); + + var glyph = this.glyphCache[charcode]; + if (!glyph || + !glyph.matchesForCache(fontChar, unicode, accent, width, vmetric, + operatorListId)) { + glyph = new Glyph(fontChar, unicode, accent, width, vmetric, + operatorListId); + this.glyphCache[charcode] = glyph; + } + return glyph; }, charsToGlyphs: function Font_charsToGlyphs(chars) { var charsCache = this.charsCache; - var glyphs; + var glyphs, glyph, charcode; // if we translated this string before, just grab it from the cache if (charsCache) { glyphs = charsCache[chars]; - if (glyphs) + if (glyphs) { return glyphs; + } } // lazily create the translation cache - if (!charsCache) + if (!charsCache) { charsCache = this.charsCache = Object.create(null); + } glyphs = []; var charsCacheKey = chars; + var i = 0, ii; if (this.cMap) { - var i = 0; // composite fonts have multi-byte strings convert the string from // single-byte to multi-byte while (i < chars.length) { var c = this.cMap.readCharCode(chars, i); - var charcode = c[0]; + charcode = c[0]; var length = c[1]; i += length; - var glyph = this.charToGlyph(charcode); + glyph = this.charToGlyph(charcode); glyphs.push(glyph); // placing null after each word break charcode (ASCII SPACE) // Ignore occurences of 0x20 in multiple-byte codes. @@ -21402,12 +23234,13 @@ var Font = (function FontClosure() { } } } else { - for (var i = 0, ii = chars.length; i < ii; ++i) { - var charcode = chars.charCodeAt(i); - var glyph = this.charToGlyph(charcode); + for (i = 0, ii = chars.length; i < ii; ++i) { + charcode = chars.charCodeAt(i); + glyph = this.charToGlyph(charcode); glyphs.push(glyph); - if (charcode == 0x20) + if (charcode == 0x20) { glyphs.push(null); + } } } @@ -21422,6 +23255,8 @@ var Font = (function FontClosure() { var ErrorFont = (function ErrorFontClosure() { function ErrorFont(error) { this.error = error; + this.loadedName = 'g_font_error'; + this.loading = false; } ErrorFont.prototype = { @@ -21447,12 +23282,14 @@ var ErrorFont = (function ErrorFontClosure() { */ function type1FontGlyphMapping(properties, builtInEncoding, glyphNames) { var charCodeToGlyphId = Object.create(null); + var glyphId, charCode, baseEncoding; + if (properties.baseEncodingName) { // If a valid base encoding name was used, the mapping is initialized with // that. - var baseEncoding = Encodings[properties.baseEncodingName]; - for (var charCode = 0; charCode < baseEncoding.length; charCode++) { - var glyphId = glyphNames.indexOf(baseEncoding[charCode]); + baseEncoding = Encodings[properties.baseEncodingName]; + for (charCode = 0; charCode < baseEncoding.length; charCode++) { + glyphId = glyphNames.indexOf(baseEncoding[charCode]); if (glyphId >= 0) { charCodeToGlyphId[charCode] = glyphId; } @@ -21460,15 +23297,15 @@ function type1FontGlyphMapping(properties, builtInEncoding, glyphNames) { } else if (!!(properties.flags & FontFlags.Symbolic)) { // For a symbolic font the encoding should be the fonts built-in // encoding. - for (var charCode in builtInEncoding) { + for (charCode in builtInEncoding) { charCodeToGlyphId[charCode] = builtInEncoding[charCode]; } } else { // For non-symbolic fonts that don't have a base encoding the standard // encoding should be used. - var baseEncoding = Encodings.StandardEncoding; - for (var charCode = 0; charCode < baseEncoding.length; charCode++) { - var glyphId = glyphNames.indexOf(baseEncoding[charCode]); + baseEncoding = Encodings.StandardEncoding; + for (charCode = 0; charCode < baseEncoding.length; charCode++) { + glyphId = glyphNames.indexOf(baseEncoding[charCode]); if (glyphId >= 0) { charCodeToGlyphId[charCode] = glyphId; } @@ -21478,9 +23315,9 @@ function type1FontGlyphMapping(properties, builtInEncoding, glyphNames) { // Lastly, merge in the differences. var differences = properties.differences; if (differences) { - for (var charCode in differences) { + for (charCode in differences) { var glyphName = differences[charCode]; - var glyphId = glyphNames.indexOf(glyphName); + glyphId = glyphNames.indexOf(glyphName); if (glyphId >= 0) { charCodeToGlyphId[charCode] = glyphId; } @@ -21558,6 +23395,7 @@ var Type1CharString = (function Type1CharStringClosure() { convert: function Type1CharString_convert(encoded, subrs) { var count = encoded.length; var error = false; + var wx, sbx, subrNumber; for (var i = 0; i < count; i++) { var value = encoded[i]; if (value < 32) { @@ -21615,7 +23453,7 @@ var Type1CharString = (function Type1CharStringClosure() { error = true; break; } - var subrNumber = this.stack.pop(); + subrNumber = this.stack.pop(); error = this.convert(subrs[subrNumber], subrs); break; case 11: // return @@ -21627,8 +23465,8 @@ var Type1CharString = (function Type1CharStringClosure() { } // To convert to type2 we have to move the width value to the // first part of the charstring and then use hmoveto with lsb. - var wx = this.stack.pop(); - var sbx = this.stack.pop(); + wx = this.stack.pop(); + sbx = this.stack.pop(); this.lsb = sbx; this.width = wx; this.stack.push(sbx); @@ -21701,9 +23539,9 @@ var Type1CharString = (function Type1CharStringClosure() { // (dx, dy). The height argument will not be used for vmtx and // vhea tables reconstruction -- ignoring it. var wy = this.stack.pop(); - var wx = this.stack.pop(); + wx = this.stack.pop(); var sby = this.stack.pop(); - var sbx = this.stack.pop(); + sbx = this.stack.pop(); this.lsb = sbx; this.width = wx; this.stack.push(sbx, sby); @@ -21723,7 +23561,7 @@ var Type1CharString = (function Type1CharStringClosure() { error = true; break; } - var subrNumber = this.stack.pop(); + subrNumber = this.stack.pop(); var numArgs = this.stack.pop(); if (subrNumber === 0 && numArgs === 3) { var flexArgs = this.stack.splice(this.stack.length - 17, 17); @@ -21804,7 +23642,7 @@ var Type1CharString = (function Type1CharStringClosure() { if (keepStack) { this.stack.splice(start, howManyArgs); } else { - this.stack = []; + this.stack.length = 0; } return false; } @@ -21830,18 +23668,46 @@ var Type1Parser = (function Type1ParserClosure() { var EEXEC_ENCRYPT_KEY = 55665; var CHAR_STRS_ENCRYPT_KEY = 4330; - function decrypt(stream, key, discardNumber) { - var r = key, c1 = 52845, c2 = 22719; - var decryptedString = []; + function isHexDigit(code) { + return code >= 48 && code <= 57 || // '0'-'9' + code >= 65 && code <= 70 || // 'A'-'F' + code >= 97 && code <= 102; // 'a'-'f' + } - var value = ''; - var count = stream.length; + function decrypt(data, key, discardNumber) { + var r = key | 0, c1 = 52845, c2 = 22719; + var count = data.length; + var decrypted = new Uint8Array(count); for (var i = 0; i < count; i++) { - value = stream[i]; - decryptedString[i] = value ^ (r >> 8); + var value = data[i]; + decrypted[i] = value ^ (r >> 8); r = ((value + r) * c1 + c2) & ((1 << 16) - 1); } - return decryptedString.slice(discardNumber); + return Array.prototype.slice.call(decrypted, discardNumber); + } + + function decryptAscii(data, key, discardNumber) { + var r = key | 0, c1 = 52845, c2 = 22719; + var count = data.length, maybeLength = count >>> 1; + var decrypted = new Uint8Array(maybeLength); + var i, j; + for (i = 0, j = 0; i < count; i++) { + var digit1 = data[i]; + if (!isHexDigit(digit1)) { + continue; + } + i++; + var digit2; + while (i < count && !isHexDigit(digit2 = data[i])) { + i++; + } + if (i < count) { + var value = parseInt(String.fromCharCode(digit1, digit2), 16); + decrypted[j++] = value ^ (r >> 8); + r = ((value + r) * c1 + c2) & ((1 << 16) - 1); + } + } + return Array.prototype.slice.call(decrypted, discardNumber, j); } function isSpecial(c) { @@ -21853,7 +23719,11 @@ var Type1Parser = (function Type1ParserClosure() { function Type1Parser(stream, encrypted) { if (encrypted) { - stream = new Stream(decrypt(stream.getBytes(), EEXEC_ENCRYPT_KEY, 4)); + var data = stream.getBytes(); + var isBinary = !(isHexDigit(data[0]) && isHexDigit(data[1]) && + isHexDigit(data[2]) && isHexDigit(data[3])); + stream = new Stream(isBinary ? decrypt(data, EEXEC_ENCRYPT_KEY, 4) : + decryptAscii(data, EEXEC_ENCRYPT_KEY, 4)); } this.stream = stream; this.nextChar(); @@ -21945,7 +23815,7 @@ var Type1Parser = (function Type1ParserClosure() { } } }; - var token; + var token, length, data, lenIV, encoded; while ((token = this.getToken()) !== null) { if (token !== '/') { continue; @@ -21969,12 +23839,11 @@ var Type1Parser = (function Type1ParserClosure() { continue; } var glyph = this.getToken(); - var length = this.readInt(); + length = this.readInt(); this.getToken(); // read in 'RD' or '-|' - var data = stream.makeSubStream(stream.pos, length); - var lenIV = program.properties.privateData['lenIV']; - var encoded = decrypt(data.getBytes(), CHAR_STRS_ENCRYPT_KEY, - lenIV); + data = stream.makeSubStream(stream.pos, length); + lenIV = program.properties.privateData['lenIV']; + encoded = decrypt(data.getBytes(), CHAR_STRS_ENCRYPT_KEY, lenIV); // Skip past the required space and binary data. stream.skip(length); this.nextChar(); @@ -21993,12 +23862,11 @@ var Type1Parser = (function Type1ParserClosure() { this.getToken(); // read in 'array' while ((token = this.getToken()) === 'dup') { var index = this.readInt(); - var length = this.readInt(); + length = this.readInt(); this.getToken(); // read in 'RD' or '-|' - var data = stream.makeSubStream(stream.pos, length); - var lenIV = program.properties.privateData['lenIV']; - var encoded = decrypt(data.getBytes(), CHAR_STRS_ENCRYPT_KEY, - lenIV); + data = stream.makeSubStream(stream.pos, length); + lenIV = program.properties.privateData['lenIV']; + encoded = decrypt(data.getBytes(), CHAR_STRS_ENCRYPT_KEY, lenIV); // Skip past the required space and binary data. stream.skip(length); this.nextChar(); @@ -22045,8 +23913,8 @@ var Type1Parser = (function Type1ParserClosure() { } for (var i = 0; i < charstrings.length; i++) { - var glyph = charstrings[i].glyph; - var encoded = charstrings[i].encoded; + glyph = charstrings[i].glyph; + encoded = charstrings[i].encoded; var charString = new Type1CharString(); var error = charString.convert(encoded, subrs); var output = charString.output; @@ -22092,7 +23960,7 @@ var Type1Parser = (function Type1ParserClosure() { this.getToken(); // read in 'array' for (var j = 0; j < size; j++) { - var token = this.getToken(); + token = this.getToken(); // skipping till first dup or def (e.g. ignoring for statement) while (token !== 'dup' && token !== 'def') { token = this.getToken(); @@ -22230,8 +24098,9 @@ var Type1Font = function Type1Font(name, file, properties) { var eexecBlock = new Stream(file.getBytes(eexecBlockLength)); var eexecBlockParser = new Type1Parser(eexecBlock, true); var data = eexecBlockParser.extractFontProgram(); - for (var info in data.properties) + for (var info in data.properties) { properties[info] = data.properties[info]; + } var charstrings = data.charstrings; var type2Charstrings = this.getType2Charstrings(charstrings); @@ -22245,7 +24114,7 @@ var Type1Font = function Type1Font(name, file, properties) { Type1Font.prototype = { get numGlyphs() { - return this.charstrings.length; + return this.charstrings.length + 1; }, getCharset: function Type1Font_getCharset() { @@ -22259,15 +24128,15 @@ Type1Font.prototype = { getGlyphMapping: function Type1Font_getGlyphMapping(properties) { var charstrings = this.charstrings; - var glyphNames = ['.notdef']; - for (var glyphId = 0; glyphId < charstrings.length; glyphId++) { + var glyphNames = ['.notdef'], glyphId; + for (glyphId = 0; glyphId < charstrings.length; glyphId++) { glyphNames.push(charstrings[glyphId].glyphName); } var encoding = properties.builtInEncoding; if (encoding) { var builtInEncoding = {}; for (var charCode in encoding) { - var glyphId = glyphNames.indexOf(encoding[charCode]); + glyphId = glyphNames.indexOf(encoding[charCode]); if (glyphId >= 0) { builtInEncoding[charCode] = glyphId; } @@ -22302,19 +24171,22 @@ Type1Font.prototype = { getType2Subrs: function Type1Font_getType2Subrs(type1Subrs) { var bias = 0; var count = type1Subrs.length; - if (count < 1133) + if (count < 1133) { bias = 107; - else if (count < 33769) + } else if (count < 33769) { bias = 1131; - else + } else { bias = 32768; + } // Add a bunch of empty subrs to deal with the Type2 bias var type2Subrs = []; - for (var i = 0; i < bias; i++) + var i; + for (i = 0; i < bias; i++) { type2Subrs.push([0x0B]); + } - for (var i = 0; i < count; i++) { + for (i = 0; i < count; i++) { type2Subrs.push(type1Subrs[i]); } @@ -22355,22 +24227,23 @@ Type1Font.prototype = { var count = glyphs.length; var charsetArray = [0]; - for (var i = 0; i < count; i++) { + var i, ii; + for (i = 0; i < count; i++) { var index = CFFStandardStrings.indexOf(charstrings[i].glyphName); // TODO: Insert the string and correctly map it. Previously it was // thought mapping names that aren't in the standard strings to .notdef // was fine, however in issue818 when mapping them all to .notdef the // adieresis glyph no longer worked. - if (index == -1) + if (index == -1) { index = 0; - + } charsetArray.push((index >> 8) & 0xff, index & 0xff); } cff.charset = new CFFCharset(false, 0, [], charsetArray); var charStringsIndex = new CFFIndex(); charStringsIndex.add([0x8B, 0x0E]); // .notdef - for (var i = 0; i < count; i++) { + for (i = 0; i < count; i++) { charStringsIndex.add(glyphs[i]); } cff.charStrings = charStringsIndex; @@ -22393,10 +24266,11 @@ Type1Font.prototype = { 'StdHW', 'StdVW' ]; - for (var i = 0, ii = fields.length; i < ii; i++) { + for (i = 0, ii = fields.length; i < ii; i++) { var field = fields[i]; - if (!properties.privateData.hasOwnProperty(field)) + if (!properties.privateData.hasOwnProperty(field)) { continue; + } var value = properties.privateData[field]; if (isArray(value)) { // All of the private dictionary array data in CFF must be stored as @@ -22410,7 +24284,7 @@ Type1Font.prototype = { cff.topDict.privateDict = privateDict; var subrIndex = new CFFIndex(); - for (var i = 0, ii = subrs.length; i < ii; i++) { + for (i = 0, ii = subrs.length; i < ii; i++) { subrIndex.add(subrs[i]); } privateDict.subrsIndex = subrIndex; @@ -22447,22 +24321,25 @@ var CFFFont = (function CFFFontClosure() { }, getGlyphMapping: function CFFFont_getGlyphMapping() { var cff = this.cff; + var properties = this.properties; var charsets = cff.charset.charset; - var charCodeToGlyphId = Object.create(null); + var charCodeToGlyphId; + var glyphId; - if (this.properties.composite) { - if (this.cff.isCIDFont) { + if (properties.composite) { + charCodeToGlyphId = Object.create(null); + if (cff.isCIDFont) { // If the font is actually a CID font then we should use the charset // to map CIDs to GIDs. - for (var glyphId = 0; glyphId < charsets.length; glyphId++) { + for (glyphId = 0; glyphId < charsets.length; glyphId++) { var cidString = String.fromCharCode(charsets[glyphId]); - var charCode = this.properties.cMap.map.indexOf(cidString); + var charCode = properties.cMap.map.indexOf(cidString); charCodeToGlyphId[charCode] = glyphId; } } else { // If it is NOT actually a CID font then CIDs should be mapped // directly to GIDs. - for (var glyphId = 0; glyphId < cff.charStrings.count; glyphId++) { + for (glyphId = 0; glyphId < cff.charStrings.count; glyphId++) { charCodeToGlyphId[glyphId] = glyphId; } } @@ -22470,7 +24347,8 @@ var CFFFont = (function CFFFontClosure() { } var encoding = cff.encoding ? cff.encoding.encoding : null; - return type1FontGlyphMapping(this.properties, encoding, charsets); + charCodeToGlyphId = type1FontGlyphMapping(properties, encoding, charsets); + return charCodeToGlyphId; } }; @@ -22652,12 +24530,17 @@ var CFFParser = (function CFFParserClosure() { }, parseHeader: function CFFParser_parseHeader() { var bytes = this.bytes; + var bytesLength = bytes.length; var offset = 0; - while (bytes[offset] != 1) + // Prevent an infinite loop, by checking that the offset is within the + // bounds of the bytes array. Necessary in empty, or invalid, font files. + while (offset < bytesLength && bytes[offset] !== 1) { ++offset; - - if (offset !== 0) { + } + if (offset >= bytesLength) { + error('Invalid CFF header'); + } else if (offset !== 0) { info('cff data is shifted'); bytes = bytes.subarray(offset); this.bytes = bytes; @@ -22667,7 +24550,7 @@ var CFFParser = (function CFFParserClosure() { var hdrSize = bytes[2]; var offSize = bytes[3]; var header = new CFFHeader(major, minor, hdrSize, offSize); - return {obj: header, endPos: hdrSize}; + return { obj: header, endPos: hdrSize }; }, parseDict: function CFFParser_parseDict(dict) { var pos = 0; @@ -22709,12 +24592,14 @@ var CFFParser = (function CFFParserClosure() { var b1 = b >> 4; var b2 = b & 15; - if (b1 == eof) + if (b1 == eof) { break; + } str += lookup[b1]; - if (b2 == eof) + if (b2 == eof) { break; + } str += lookup[b2]; } return parseFloat(str); @@ -22723,13 +24608,14 @@ var CFFParser = (function CFFParserClosure() { var operands = []; var entries = []; - var pos = 0; + pos = 0; var end = dict.length; while (pos < end) { var b = dict[pos]; if (b <= 21) { - if (b === 12) + if (b === 12) { b = (b << 8) | dict[++pos]; + } entries.push([b, operands]); operands = []; ++pos; @@ -22744,15 +24630,15 @@ var CFFParser = (function CFFParserClosure() { var bytes = this.bytes; var count = (bytes[pos++] << 8) | bytes[pos++]; var offsets = []; - var start = pos; var end = pos; + var i, ii; if (count !== 0) { var offsetSize = bytes[pos++]; // add 1 for offset to determine size of last object var startPos = pos + ((count + 1) * offsetSize) - 1; - for (var i = 0, ii = count + 1; i < ii; ++i) { + for (i = 0, ii = count + 1; i < ii; ++i) { var offset = 0; for (var j = 0; j < offsetSize; ++j) { offset <<= 8; @@ -22762,7 +24648,7 @@ var CFFParser = (function CFFParserClosure() { } end = offsets[count]; } - for (var i = 0, ii = offsets.length - 1; i < ii; ++i) { + for (i = 0, ii = offsets.length - 1; i < ii; ++i) { var offsetStart = offsets[i]; var offsetEnd = offsets[i + 1]; cffIndex.add(bytes.subarray(offsetStart, offsetEnd)); @@ -22786,13 +24672,13 @@ var CFFParser = (function CFFParserClosure() { if ((c < 33 || c > 126) || c === 91 /* [ */ || c === 93 /* ] */ || c === 40 /* ( */ || c === 41 /* ) */ || c === 123 /* { */ || c === 125 /* } */ || c === 60 /* < */ || c === 62 /* > */ || - c === 47 /* / */ || c === 37 /* % */) { + c === 47 /* / */ || c === 37 /* % */ || c === 35 /* # */) { data[j] = 95; continue; } data[j] = c; } - names.push(String.fromCharCode.apply(null, data)); + names.push(bytesToString(data)); } return names; }, @@ -22800,14 +24686,12 @@ var CFFParser = (function CFFParserClosure() { var strings = new CFFStrings(); for (var i = 0, ii = index.count; i < ii; ++i) { var data = index.get(i); - strings.add(String.fromCharCode.apply(null, data)); + strings.add(bytesToString(data)); } return strings; }, createDict: function CFFParser_createDict(Type, dict, strings) { var cffDict = new Type(strings); - var types = cffDict.types; - for (var i = 0, ii = dict.length; i < ii; ++i) { var pair = dict[i]; var key = pair[0]; @@ -22863,14 +24747,14 @@ var CFFParser = (function CFFParserClosure() { stack[stackSize] = value - 139; stackSize++; } else if (value >= 247 && value <= 254) { // number (+1 bytes) - stack[stackSize] = value < 251 ? - ((value - 247) << 8) + data[j] + 108 : - -((value - 251) << 8) - data[j] - 108; + stack[stackSize] = (value < 251 ? + ((value - 247) << 8) + data[j] + 108 : + -((value - 251) << 8) - data[j] - 108); j++; stackSize++; } else if (value == 255) { // number (32 bit) stack[stackSize] = ((data[j] << 24) | (data[j + 1] << 16) | - (data[j + 2] << 8) | data[j + 3]) / 65536; + (data[j + 2] << 8) | data[j + 3]) / 65536; j += 4; stackSize++; } else if (value == 19 || value == 20) { @@ -22949,8 +24833,9 @@ var CFFParser = (function CFFParserClosure() { parentDict.privateDict = privateDict; // Parse the Subrs index also since it's relative to the private dict. - if (!privateDict.getByName('Subrs')) + if (!privateDict.getByName('Subrs')) { return; + } var subrsOffset = privateDict.getByName('Subrs'); var relativeOffset = offset + subrsOffset; // Validate the offset. @@ -22977,31 +24862,34 @@ var CFFParser = (function CFFParserClosure() { var start = pos; var format = bytes[pos++]; var charset = ['.notdef']; + var id, count, i; // subtract 1 for the .notdef glyph length -= 1; switch (format) { case 0: - for (var i = 0; i < length; i++) { - var id = (bytes[pos++] << 8) | bytes[pos++]; + for (i = 0; i < length; i++) { + id = (bytes[pos++] << 8) | bytes[pos++]; charset.push(cid ? id : strings.get(id)); } break; case 1: while (charset.length <= length) { - var id = (bytes[pos++] << 8) | bytes[pos++]; - var count = bytes[pos++]; - for (var i = 0; i <= count; i++) + id = (bytes[pos++] << 8) | bytes[pos++]; + count = bytes[pos++]; + for (i = 0; i <= count; i++) { charset.push(cid ? id++ : strings.get(id++)); + } } break; case 2: while (charset.length <= length) { - var id = (bytes[pos++] << 8) | bytes[pos++]; - var count = (bytes[pos++] << 8) | bytes[pos++]; - for (var i = 0; i <= count; i++) + id = (bytes[pos++] << 8) | bytes[pos++]; + count = (bytes[pos++] << 8) | bytes[pos++]; + for (i = 0; i <= count; i++) { charset.push(cid ? id++ : strings.get(id++)); + } } break; default: @@ -23021,12 +24909,12 @@ var CFFParser = (function CFFParserClosure() { var bytes = this.bytes; var predefined = false; var hasSupplement = false; - var format; + var format, i, ii; var raw = null; function readSupplement() { var supplementsCount = bytes[pos++]; - for (var i = 0; i < supplementsCount; i++) { + for (i = 0; i < supplementsCount; i++) { var code = bytes[pos++]; var sid = (bytes[pos++] << 8) + (bytes[pos++] & 0xff); encoding[code] = charset.indexOf(strings.get(sid)); @@ -23038,7 +24926,7 @@ var CFFParser = (function CFFParserClosure() { format = pos; var baseEncoding = pos ? Encodings.ExpertEncoding : Encodings.StandardEncoding; - for (var i = 0, ii = charset.length; i < ii; i++) { + for (i = 0, ii = charset.length; i < ii; i++) { var index = baseEncoding.indexOf(charset[i]); if (index != -1) { encoding[index] = i; @@ -23046,22 +24934,24 @@ var CFFParser = (function CFFParserClosure() { } } else { var dataStart = pos; - var format = bytes[pos++]; + format = bytes[pos++]; switch (format & 0x7f) { case 0: var glyphsCount = bytes[pos++]; - for (var i = 1; i <= glyphsCount; i++) + for (i = 1; i <= glyphsCount; i++) { encoding[bytes[pos++]] = i; + } break; case 1: var rangesCount = bytes[pos++]; var gid = 1; - for (var i = 0; i < rangesCount; i++) { + for (i = 0; i < rangesCount; i++) { var start = bytes[pos++]; var left = bytes[pos++]; - for (var j = start; j <= start + left; j++) + for (var j = start; j <= start + left; j++) { encoding[j] = gid++; + } } break; @@ -23090,21 +24980,24 @@ var CFFParser = (function CFFParserClosure() { var bytes = this.bytes; var format = bytes[pos++]; var fdSelect = []; + var i; + switch (format) { case 0: - for (var i = 0; i < length; ++i) { + for (i = 0; i < length; ++i) { var id = bytes[pos++]; fdSelect.push(id); } break; case 3: var rangesCount = (bytes[pos++] << 8) | bytes[pos++]; - for (var i = 0; i < rangesCount; ++i) { + for (i = 0; i < rangesCount; ++i) { var first = (bytes[pos++] << 8) | bytes[pos++]; var fdIndex = bytes[pos++]; var next = (bytes[pos] << 8) | bytes[pos + 1]; - for (var j = first; j < next; ++j) + for (var j = first; j < next; ++j) { fdSelect.push(fdIndex); + } } // Advance past the sentinel(next). pos += 2; @@ -23158,10 +25051,12 @@ var CFFStrings = (function CFFStringsClosure() { } CFFStrings.prototype = { get: function CFFStrings_get(index) { - if (index >= 0 && index <= 390) + if (index >= 0 && index <= 390) { return CFFStandardStrings[index]; - if (index - 391 <= this.strings.length) + } + if (index - 391 <= this.strings.length) { return this.strings[index - 391]; + } return CFFStandardStrings[0]; }, add: function CFFStrings_add(value) { @@ -23212,15 +25107,18 @@ var CFFDict = (function CFFDictClosure() { CFFDict.prototype = { // value should always be an array setByKey: function CFFDict_setByKey(key, value) { - if (!(key in this.keyToNameMap)) + if (!(key in this.keyToNameMap)) { return false; + } // ignore empty values - if (value.length === 0) + if (value.length === 0) { return true; + } var type = this.types[key]; // remove the array wrapping these types of values - if (type === 'num' || type === 'sid' || type === 'offset') + if (type === 'num' || type === 'sid' || type === 'offset') { value = value[0]; + } this.values[key] = value; return true; }, @@ -23234,11 +25132,13 @@ var CFFDict = (function CFFDictClosure() { return this.nameToKeyMap[name] in this.values; }, getByName: function CFFDict_getByName(name) { - if (!(name in this.nameToKeyMap)) + if (!(name in this.nameToKeyMap)) { error('Invalid dictionary name "' + name + '"'); + } var key = this.nameToKeyMap[name]; - if (!(key in this.values)) + if (!(key in this.values)) { return this.defaults[key]; + } return this.values[key]; }, removeByName: function CFFDict_removeByName(name) { @@ -23311,8 +25211,9 @@ var CFFTopDict = (function CFFTopDictClosure() { ]; var tables = null; function CFFTopDict(strings) { - if (tables === null) + if (tables === null) { tables = CFFDict.createTables(layout); + } CFFDict.call(this, tables, strings); this.privateDict = null; } @@ -23343,8 +25244,9 @@ var CFFPrivateDict = (function CFFPrivateDictClosure() { ]; var tables = null; function CFFPrivateDict(strings) { - if (tables === null) + if (tables === null) { tables = CFFDict.createTables(layout); + } CFFDict.call(this, tables, strings); this.subrsIndex = null; } @@ -23357,11 +25259,6 @@ var CFFCharsetPredefinedTypes = { EXPERT: 1, EXPERT_SUBSET: 2 }; -var CFFCharsetEmbeddedTypes = { - FORMAT0: 0, - FORMAT1: 1, - FORMAT2: 2 -}; var CFFCharset = (function CFFCharsetClosure() { function CFFCharset(predefined, format, charset, raw) { this.predefined = predefined; @@ -23372,14 +25269,6 @@ var CFFCharset = (function CFFCharsetClosure() { return CFFCharset; })(); -var CFFEncodingPredefinedTypes = { - STANDARD: 0, - EXPERT: 1 -}; -var CFFCharsetEmbeddedTypes = { - FORMAT0: 0, - FORMAT1: 1 -}; var CFFEncoding = (function CFFEncodingClosure() { function CFFEncoding(predefined, format, encoding, raw) { this.predefined = predefined; @@ -23409,8 +25298,9 @@ var CFFOffsetTracker = (function CFFOffsetTrackerClosure() { return key in this.offsets; }, track: function CFFOffsetTracker_track(key, location) { - if (key in this.offsets) + if (key in this.offsets) { error('Already tracking location of ' + key); + } this.offsets[key] = location; }, offset: function CFFOffsetTracker_offset(value) { @@ -23421,8 +25311,9 @@ var CFFOffsetTracker = (function CFFOffsetTrackerClosure() { setEntryLocation: function CFFOffsetTracker_setEntryLocation(key, values, output) { - if (!(key in this.offsets)) + if (!(key in this.offsets)) { error('Not tracking location of ' + key); + } var data = output.data; var dataOffset = this.offsets[key]; var size = 5; @@ -23434,8 +25325,9 @@ var CFFOffsetTracker = (function CFFOffsetTrackerClosure() { var offset4 = offset0 + 4; // It's easy to screw up offsets so perform this sanity check. if (data[offset0] !== 0x1d || data[offset1] !== 0 || - data[offset2] !== 0 || data[offset3] !== 0 || data[offset4] !== 0) + data[offset2] !== 0 || data[offset3] !== 0 || data[offset4] !== 0) { error('writing to an offset that is not empty'); + } var value = values[i]; data[offset0] = 0x1d; data[offset1] = (value >> 24) & 0xFF; @@ -23450,13 +25342,6 @@ var CFFOffsetTracker = (function CFFOffsetTrackerClosure() { // Takes a CFF and converts it to the binary representation. var CFFCompiler = (function CFFCompilerClosure() { - function stringToArray(str) { - var array = []; - for (var i = 0, ii = str.length; i < ii; ++i) - array[i] = str.charCodeAt(i); - - return array; - } function CFFCompiler(cff) { this.cff = cff; } @@ -23553,7 +25438,7 @@ var CFFCompiler = (function CFFCompilerClosure() { output.add(fdSelect); // It is unclear if the sub font dictionary can have CID related // dictionary keys, but the sanitizer doesn't like them so remove them. - var compiled = this.compileTopDicts(cff.fdArray, output.length, true); + compiled = this.compileTopDicts(cff.fdArray, output.length, true); topDictTracker.setEntryLocation('FDArray', [output.length], output); output.add(compiled.output); var fontDictTrackers = compiled.trackers; @@ -23570,10 +25455,11 @@ var CFFCompiler = (function CFFCompilerClosure() { return output.data; }, encodeNumber: function CFFCompiler_encodeNumber(value) { - if (parseFloat(value) == parseInt(value, 10) && !isNaN(value)) // isInt + if (parseFloat(value) == parseInt(value, 10) && !isNaN(value)) { // isInt return this.encodeInteger(value); - else + } else { return this.encodeFloat(value); + } }, encodeFloat: function CFFCompiler_encodeFloat(num) { var value = num.toString(); @@ -23586,7 +25472,8 @@ var CFFCompiler = (function CFFCompilerClosure() { } var nibbles = ''; - for (var i = 0, ii = value.length; i < ii; ++i) { + var i, ii; + for (i = 0, ii = value.length; i < ii; ++i) { var a = value[i]; if (a === 'e') { nibbles += value[++i] === '-' ? 'c' : 'b'; @@ -23600,7 +25487,7 @@ var CFFCompiler = (function CFFCompilerClosure() { } nibbles += (nibbles.length & 1) ? 'f' : 'ff'; var out = [30]; - for (var i = 0, ii = nibbles.length; i < ii; i += 2) { + for (i = 0, ii = nibbles.length; i < ii; i += 2) { out.push(parseInt(nibbles.substr(i, 2), 16)); } return out; @@ -23636,8 +25523,9 @@ var CFFCompiler = (function CFFCompilerClosure() { }, compileNameIndex: function CFFCompiler_compileNameIndex(names) { var nameIndex = new CFFIndex(); - for (var i = 0, ii = names.length; i < ii; ++i) + for (var i = 0, ii = names.length; i < ii; ++i) { nameIndex.add(stringToArray(names[i])); + } return this.compileIndex(nameIndex); }, compileTopDicts: function CFFCompiler_compileTopDicts(dicts, @@ -23705,16 +25593,22 @@ var CFFCompiler = (function CFFCompilerClosure() { var order = dict.order; for (var i = 0; i < order.length; ++i) { var key = order[i]; - if (!(key in dict.values)) + if (!(key in dict.values)) { continue; + } var values = dict.values[key]; var types = dict.types[key]; - if (!isArray(types)) types = [types]; - if (!isArray(values)) values = [values]; + if (!isArray(types)) { + types = [types]; + } + if (!isArray(values)) { + values = [values]; + } // Remove any empty dict values. - if (values.length === 0) + if (values.length === 0) { continue; + } for (var j = 0, jj = types.length; j < jj; ++j) { var type = types[j]; @@ -23731,15 +25625,17 @@ var CFFCompiler = (function CFFCompilerClosure() { var name = dict.keyToNameMap[key]; // Some offsets have the offset and the length, so just record the // position of the first one. - if (!offsetTracker.isTracking(name)) + if (!offsetTracker.isTracking(name)) { offsetTracker.track(name, out.length); + } out = out.concat([0x1d, 0, 0, 0, 0]); break; case 'array': case 'delta': out = out.concat(this.encodeNumber(value)); - for (var k = 1, kk = values.length; k < kk; ++k) + for (var k = 1, kk = values.length; k < kk; ++k) { out = out.concat(this.encodeNumber(values[k])); + } break; default: error('Unknown data type of ' + type); @@ -23752,8 +25648,9 @@ var CFFCompiler = (function CFFCompilerClosure() { }, compileStringIndex: function CFFCompiler_compileStringIndex(strings) { var stringIndex = new CFFIndex(); - for (var i = 0, ii = strings.length; i < ii; ++i) + for (var i = 0, ii = strings.length; i < ii; ++i) { stringIndex.add(stringToArray(strings[i])); + } return this.compileIndex(stringIndex); }, compileGlobalSubrIndex: function CFFCompiler_compileGlobalSubrIndex() { @@ -23774,8 +25671,9 @@ var CFFCompiler = (function CFFCompilerClosure() { }, compileTypedArray: function CFFCompiler_compileTypedArray(data) { var out = []; - for (var i = 0, ii = data.length; i < ii; ++i) + for (var i = 0, ii = data.length; i < ii; ++i) { out[i] = data[i]; + } return out; }, compileIndex: function CFFCompiler_compileIndex(index, trackers) { @@ -23786,31 +25684,34 @@ var CFFCompiler = (function CFFCompilerClosure() { // If there is no object, just create an index. This technically // should just be [0, 0] but OTS has an issue with that. - if (count === 0) + if (count === 0) { return [0, 0, 0]; + } var data = [(count >> 8) & 0xFF, count & 0xff]; - var lastOffset = 1; - for (var i = 0; i < count; ++i) + var lastOffset = 1, i; + for (i = 0; i < count; ++i) { lastOffset += objects[i].length; + } var offsetSize; - if (lastOffset < 0x100) + if (lastOffset < 0x100) { offsetSize = 1; - else if (lastOffset < 0x10000) + } else if (lastOffset < 0x10000) { offsetSize = 2; - else if (lastOffset < 0x1000000) + } else if (lastOffset < 0x1000000) { offsetSize = 3; - else + } else { offsetSize = 4; + } // Next byte contains the offset size use to reference object in the file data.push(offsetSize); // Add another offset after this one because we need a new offset var relativeOffset = 1; - for (var i = 0; i < count + 1; i++) { + for (i = 0; i < count + 1; i++) { if (offsetSize === 1) { data.push(relativeOffset & 0xFF); } else if (offsetSize === 2) { @@ -23827,17 +25728,19 @@ var CFFCompiler = (function CFFCompilerClosure() { relativeOffset & 0xFF); } - if (objects[i]) + if (objects[i]) { relativeOffset += objects[i].length; + } } - var offset = data.length; - for (var i = 0; i < count; i++) { + for (i = 0; i < count; i++) { // Notify the tracker where the object will be offset in the data. - if (trackers[i]) + if (trackers[i]) { trackers[i].offset(data.length); - for (var j = 0, jj = objects[i].length; j < jj; j++) + } + for (var j = 0, jj = objects[i].length; j < jj; j++) { data.push(objects[i][j]); + } } return data; } @@ -23873,25 +25776,26 @@ var FontRendererFactory = (function FontRendererFactoryClosure() { } function parseCmap(data, start, end) { - var offset = getUshort(data, start + 2) === 1 ? getLong(data, start + 8) : - getLong(data, start + 16); + var offset = (getUshort(data, start + 2) === 1 ? + getLong(data, start + 8) : getLong(data, start + 16)); var format = getUshort(data, start + offset); + var length, ranges, p, i; if (format === 4) { - var length = getUshort(data, start + offset + 2); + length = getUshort(data, start + offset + 2); var segCount = getUshort(data, start + offset + 6) >> 1; - var p = start + offset + 14; - var ranges = []; - for (var i = 0; i < segCount; i++, p += 2) { + p = start + offset + 14; + ranges = []; + for (i = 0; i < segCount; i++, p += 2) { ranges[i] = {end: getUshort(data, p)}; } p += 2; - for (var i = 0; i < segCount; i++, p += 2) { + for (i = 0; i < segCount; i++, p += 2) { ranges[i].start = getUshort(data, p); } - for (var i = 0; i < segCount; i++, p += 2) { + for (i = 0; i < segCount; i++, p += 2) { ranges[i].idDelta = getUshort(data, p); } - for (var i = 0; i < segCount; i++, p += 2) { + for (i = 0; i < segCount; i++, p += 2) { var idOffset = getUshort(data, p); if (idOffset === 0) { continue; @@ -23904,11 +25808,11 @@ var FontRendererFactory = (function FontRendererFactoryClosure() { } return ranges; } else if (format === 12) { - var length = getLong(data, start + offset + 4); + length = getLong(data, start + offset + 4); var groups = getLong(data, start + offset + 12); - var p = start + offset + 16; - var ranges = []; - for (var i = 0; i < groups; i++) { + p = start + offset + 16; + ranges = []; + for (i = 0; i < groups; i++) { ranges.push({ start: getLong(data, p), end: getLong(data, p + 4), @@ -23923,13 +25827,13 @@ var FontRendererFactory = (function FontRendererFactoryClosure() { function parseCff(data, start, end) { var properties = {}; - var parser = new CFFParser( - new Stream(data, start, end - start), properties); + var parser = new CFFParser(new Stream(data, start, end - start), + properties); var cff = parser.parse(); return { glyphs: cff.charStrings.objects, - subrs: cff.topDict.privateDict && cff.topDict.privateDict.subrsIndex && - cff.topDict.privateDict.subrsIndex.objects, + subrs: (cff.topDict.privateDict && cff.topDict.privateDict.subrsIndex && + cff.topDict.privateDict.subrsIndex.objects), gsubrs: cff.globalSubrIndex && cff.globalSubrIndex.objects }; } @@ -23990,16 +25894,13 @@ var FontRendererFactory = (function FontRendererFactoryClosure() { var i = 0; var numberOfContours = ((code[i] << 24) | (code[i + 1] << 16)) >> 16; - var xMin = ((code[i + 2] << 24) | (code[i + 3] << 16)) >> 16; - var yMin = ((code[i + 4] << 24) | (code[i + 5] << 16)) >> 16; - var xMax = ((code[i + 6] << 24) | (code[i + 7] << 16)) >> 16; - var yMax = ((code[i + 8] << 24) | (code[i + 9] << 16)) >> 16; + var flags; + var x = 0, y = 0; i += 10; if (numberOfContours < 0) { // composite glyph - var x = 0, y = 0; do { - var flags = (code[i] << 8) | code[i + 1]; + flags = (code[i] << 8) | code[i + 1]; var glyphIndex = (code[i + 2] << 8) | code[i + 3]; i += 4; var arg1, arg2; @@ -24044,7 +25945,8 @@ var FontRendererFactory = (function FontRendererFactoryClosure() { } else { // simple glyph var endPtsOfContours = []; - for (var j = 0; j < numberOfContours; j++) { + var j, jj; + for (j = 0; j < numberOfContours; j++) { endPtsOfContours.push((code[i] << 8) | code[i + 1]); i += 2; } @@ -24053,7 +25955,8 @@ var FontRendererFactory = (function FontRendererFactoryClosure() { var numberOfPoints = endPtsOfContours[endPtsOfContours.length - 1] + 1; var points = []; while (points.length < numberOfPoints) { - var flags = code[i++], repeat = 1; + flags = code[i++]; + var repeat = 1; if ((flags & 0x08)) { repeat += code[i++]; } @@ -24061,8 +25964,7 @@ var FontRendererFactory = (function FontRendererFactoryClosure() { points.push({flags: flags}); } } - var x = 0, y = 0; - for (var j = 0; j < numberOfPoints; j++) { + for (j = 0; j < numberOfPoints; j++) { switch (points[j].flags & 0x12) { case 0x00: x += ((code[i] << 24) | (code[i + 1] << 16)) >> 16; @@ -24077,7 +25979,7 @@ var FontRendererFactory = (function FontRendererFactoryClosure() { } points[j].x = x; } - for (var j = 0; j < numberOfPoints; j++) { + for (j = 0; j < numberOfPoints; j++) { switch (points[j].flags & 0x24) { case 0x00: y += ((code[i] << 24) | (code[i + 1] << 16)) >> 16; @@ -24094,7 +25996,7 @@ var FontRendererFactory = (function FontRendererFactoryClosure() { } var startPoint = 0; - for (var i = 0; i < numberOfContours; i++) { + for (i = 0; i < numberOfContours; i++) { var endPoint = endPtsOfContours[i]; // contours might have implicit points, which is located in the middle // between two neighboring off-curve points @@ -24115,7 +26017,7 @@ var FontRendererFactory = (function FontRendererFactoryClosure() { contour.push(p); } moveTo(contour[0].x, contour[0].y); - for (var j = 1, jj = contour.length; j < jj; j++) { + for (j = 1, jj = contour.length; j < jj; j++) { if ((contour[j].flags & 1)) { lineTo(contour[j].x, contour[j].y); } else if ((contour[j + 1].flags & 1)){ @@ -24154,6 +26056,7 @@ var FontRendererFactory = (function FontRendererFactoryClosure() { while (i < code.length) { var stackClean = false; var v = code[i++]; + var xa, xb, ya, yb, y1, y2, y3, n, subrCode; switch (v) { case 1: // hstem stems += stack.length >> 1; @@ -24199,15 +26102,15 @@ var FontRendererFactory = (function FontRendererFactoryClosure() { break; case 8: // rrcurveto while (stack.length > 0) { - var xa = x + stack.shift(), ya = y + stack.shift(); - var xb = xa + stack.shift(), yb = ya + stack.shift(); + xa = x + stack.shift(); ya = y + stack.shift(); + xb = xa + stack.shift(); yb = ya + stack.shift(); x = xb + stack.shift(); y = yb + stack.shift(); bezierCurveTo(xa, ya, xb, yb, x, y); } break; case 10: // callsubr - var n = stack.pop() + font.subrsBias; - var subrCode = font.subrs[n]; + n = stack.pop() + font.subrsBias; + subrCode = font.subrs[n]; if (subrCode) { parse(subrCode); } @@ -24218,49 +26121,50 @@ var FontRendererFactory = (function FontRendererFactoryClosure() { v = code[i++]; switch (v) { case 34: // flex - var xa = x + stack.shift(); - var xb = xa + stack.shift(), y1 = y + stack.shift(); + xa = x + stack.shift(); + xb = xa + stack.shift(); y1 = y + stack.shift(); x = xb + stack.shift(); bezierCurveTo(xa, y, xb, y1, x, y1); - var xa = x + stack.shift(); - var xb = xa + stack.shift(); + xa = x + stack.shift(); + xb = xa + stack.shift(); x = xb + stack.shift(); bezierCurveTo(xa, y1, xb, y, x, y); break; case 35: // flex - var xa = x + stack.shift(), ya = y + stack.shift(); - var xb = xa + stack.shift(), yb = ya + stack.shift(); + xa = x + stack.shift(); ya = y + stack.shift(); + xb = xa + stack.shift(); yb = ya + stack.shift(); x = xb + stack.shift(); y = yb + stack.shift(); bezierCurveTo(xa, ya, xb, yb, x, y); - var xa = x + stack.shift(), ya = y + stack.shift(); - var xb = xa + stack.shift(), yb = ya + stack.shift(); + xa = x + stack.shift(); ya = y + stack.shift(); + xb = xa + stack.shift(); yb = ya + stack.shift(); x = xb + stack.shift(); y = yb + stack.shift(); bezierCurveTo(xa, ya, xb, yb, x, y); stack.pop(); // fd break; case 36: // hflex1 - var xa = x + stack.shift(), y1 = y + stack.shift(); - var xb = xa + stack.shift(), y2 = y1 + stack.shift(); + xa = x + stack.shift(); y1 = y + stack.shift(); + xb = xa + stack.shift(); y2 = y1 + stack.shift(); x = xb + stack.shift(); bezierCurveTo(xa, y1, xb, y2, x, y2); - var xa = x + stack.shift(); - var xb = xa + stack.shift(), y3 = y2 + stack.shift(); + xa = x + stack.shift(); + xb = xa + stack.shift(); y3 = y2 + stack.shift(); x = xb + stack.shift(); bezierCurveTo(xa, y2, xb, y3, x, y); break; case 37: // flex1 var x0 = x, y0 = y; - var xa = x + stack.shift(), ya = y + stack.shift(); - var xb = xa + stack.shift(), yb = ya + stack.shift(); + xa = x + stack.shift(); ya = y + stack.shift(); + xb = xa + stack.shift(); yb = ya + stack.shift(); x = xb + stack.shift(); y = yb + stack.shift(); bezierCurveTo(xa, ya, xb, yb, x, y); - var xa = x + stack.shift(), ya = y + stack.shift(); - var xb = xa + stack.shift(), yb = ya + stack.shift(); + xa = x + stack.shift(); ya = y + stack.shift(); + xb = xa + stack.shift(); yb = ya + stack.shift(); x = xb; y = yb; - if (Math.abs(x - x0) > Math.abs(y - y0)) + if (Math.abs(x - x0) > Math.abs(y - y0)) { x += stack.shift(); - else + } else { y += stack.shift(); + } bezierCurveTo(xa, ya, xb, yb, x, y); break; default: @@ -24316,8 +26220,8 @@ var FontRendererFactory = (function FontRendererFactoryClosure() { break; case 24: // rcurveline while (stack.length > 2) { - var xa = x + stack.shift(), ya = y + stack.shift(); - var xb = xa + stack.shift(), yb = ya + stack.shift(); + xa = x + stack.shift(); ya = y + stack.shift(); + xb = xa + stack.shift(); yb = ya + stack.shift(); x = xb + stack.shift(); y = yb + stack.shift(); bezierCurveTo(xa, ya, xb, yb, x, y); } @@ -24331,8 +26235,8 @@ var FontRendererFactory = (function FontRendererFactoryClosure() { y += stack.shift(); lineTo(x, y); } - var xa = x + stack.shift(), ya = y + stack.shift(); - var xb = xa + stack.shift(), yb = ya + stack.shift(); + xa = x + stack.shift(); ya = y + stack.shift(); + xb = xa + stack.shift(); yb = ya + stack.shift(); x = xb + stack.shift(); y = yb + stack.shift(); bezierCurveTo(xa, ya, xb, yb, x, y); break; @@ -24341,8 +26245,8 @@ var FontRendererFactory = (function FontRendererFactoryClosure() { x += stack.shift(); } while (stack.length > 0) { - var xa = x, ya = y + stack.shift(); - var xb = xa + stack.shift(), yb = ya + stack.shift(); + xa = x; ya = y + stack.shift(); + xb = xa + stack.shift(); yb = ya + stack.shift(); x = xb; y = yb + stack.shift(); bezierCurveTo(xa, ya, xb, yb, x, y); } @@ -24352,8 +26256,8 @@ var FontRendererFactory = (function FontRendererFactoryClosure() { y += stack.shift(); } while (stack.length > 0) { - var xa = x + stack.shift(), ya = y; - var xb = xa + stack.shift(), yb = ya + stack.shift(); + xa = x + stack.shift(); ya = y; + xb = xa + stack.shift(); yb = ya + stack.shift(); x = xb + stack.shift(); y = yb; bezierCurveTo(xa, ya, xb, yb, x, y); } @@ -24363,16 +26267,16 @@ var FontRendererFactory = (function FontRendererFactoryClosure() { i += 2; break; case 29: // callgsubr - var n = stack.pop() + font.gsubrsBias; - var subrCode = font.gsubrs[n]; + n = stack.pop() + font.gsubrsBias; + subrCode = font.gsubrs[n]; if (subrCode) { parse(subrCode); } break; case 30: // vhcurveto while (stack.length > 0) { - var xa = x, ya = y + stack.shift(); - var xb = xa + stack.shift(), yb = ya + stack.shift(); + xa = x; ya = y + stack.shift(); + xb = xa + stack.shift(); yb = ya + stack.shift(); x = xb + stack.shift(); y = yb + (stack.length === 1 ? stack.shift() : 0); bezierCurveTo(xa, ya, xb, yb, x, y); @@ -24380,8 +26284,8 @@ var FontRendererFactory = (function FontRendererFactoryClosure() { break; } - var xa = x + stack.shift(), ya = y; - var xb = xa + stack.shift(), yb = ya + stack.shift(); + xa = x + stack.shift(); ya = y; + xb = xa + stack.shift(); yb = ya + stack.shift(); y = yb + stack.shift(); x = xb + (stack.length === 1 ? stack.shift() : 0); bezierCurveTo(xa, ya, xb, yb, x, y); @@ -24389,8 +26293,8 @@ var FontRendererFactory = (function FontRendererFactoryClosure() { break; case 31: // hvcurveto while (stack.length > 0) { - var xa = x + stack.shift(), ya = y; - var xb = xa + stack.shift(), yb = ya + stack.shift(); + xa = x + stack.shift(); ya = y; + xb = xa + stack.shift(); yb = ya + stack.shift(); y = yb + stack.shift(); x = xb + (stack.length === 1 ? stack.shift() : 0); bezierCurveTo(xa, ya, xb, yb, x, y); @@ -24398,23 +26302,24 @@ var FontRendererFactory = (function FontRendererFactoryClosure() { break; } - var xa = x, ya = y + stack.shift(); - var xb = xa + stack.shift(), yb = ya + stack.shift(); + xa = x; ya = y + stack.shift(); + xb = xa + stack.shift(); yb = ya + stack.shift(); x = xb + stack.shift(); y = yb + (stack.length === 1 ? stack.shift() : 0); bezierCurveTo(xa, ya, xb, yb, x, y); } break; default: - if (v < 32) + if (v < 32) { error('unknown operator: ' + v); - if (v < 247) + } + if (v < 247) { stack.push(v - 139); - else if (v < 251) + } else if (v < 251) { stack.push((v - 247) * 256 + code[i++] + 108); - else if (v < 255) + } else if (v < 255) { stack.push(-(v - 251) * 256 - code[i++] - 108); - else { + } else { stack.push(((code[i] << 24) | (code[i + 1] << 16) | (code[i + 2] << 8) | code[i + 3]) / 65536); i += 4; @@ -24498,10 +26403,10 @@ var FontRendererFactory = (function FontRendererFactoryClosure() { this.glyphNameMap = glyphNameMap || GlyphsUnicode; this.compiledGlyphs = []; - this.gsubrsBias = this.gsubrs.length < 1240 ? 107 : - this.gsubrs.length < 33900 ? 1131 : 32768; - this.subrsBias = this.subrs.length < 1240 ? 107 : - this.subrs.length < 33900 ? 1131 : 32768; + this.gsubrsBias = (this.gsubrs.length < 1240 ? + 107 : (this.gsubrs.length < 33900 ? 1131 : 32768)); + this.subrsBias = (this.subrs.length < 1240 ? + 107 : (this.subrs.length < 33900 ? 1131 : 32768)); } Util.inherit(Type2Compiled, CompiledFont, { @@ -24517,7 +26422,7 @@ var FontRendererFactory = (function FontRendererFactoryClosure() { var cmap, glyf, loca, cff, indexToLocFormat, unitsPerEm; var numTables = getUshort(data, 4); for (var i = 0, p = 12; i < numTables; i++, p += 16) { - var tag = String.fromCharCode.apply(null, data.subarray(p, p + 4)); + var tag = bytesToString(data.subarray(p, p + 4)); var offset = getLong(data, p + 8); var length = getLong(data, p + 12); switch (tag) { @@ -24541,8 +26446,8 @@ var FontRendererFactory = (function FontRendererFactoryClosure() { } if (glyf) { - var fontMatrix = !unitsPerEm ? font.fontMatrix : - [1 / unitsPerEm, 0, 0, 1 / unitsPerEm, 0, 0]; + var fontMatrix = (!unitsPerEm ? font.fontMatrix : + [1 / unitsPerEm, 0, 0, 1 / unitsPerEm, 0, 0]); return new TrueTypeCompiled( parseGlyfTable(glyf, loca, indexToLocFormat), cmap, fontMatrix); } else { @@ -28768,22 +30673,24 @@ var PDFImage = (function PDFImageClosure() { * Decode the image in the main thread if it supported. Resovles the promise * when the image data is ready. */ - function handleImageData(handler, xref, res, image, promise) { + function handleImageData(handler, xref, res, image) { if (image instanceof JpegStream && image.isNativelyDecodable(xref, res)) { // For natively supported jpegs send them to the main thread for decoding. var dict = image.dict; var colorSpace = dict.get('ColorSpace', 'CS'); colorSpace = ColorSpace.parse(colorSpace, xref, res); var numComps = colorSpace.numComps; - handler.send('JpegDecode', [image.getIR(), numComps], function(message) { + var decodePromise = handler.sendWithPromise('JpegDecode', + [image.getIR(), numComps]); + return decodePromise.then(function (message) { var data = message.data; - var stream = new Stream(data, 0, data.length, image.dict); - promise.resolve(stream); + return new Stream(data, 0, data.length, image.dict); }); } else { - promise.resolve(image); + return Promise.resolve(image); } } + /** * Decode and clamp a value. The formula is different from the spec because we * don't decode to float range [0,1], we decode it in the [0,max] range. @@ -28791,20 +30698,27 @@ var PDFImage = (function PDFImageClosure() { function decodeAndClamp(value, addend, coefficient, max) { value = addend + value * coefficient; // Clamp the value to the range - return value < 0 ? 0 : value > max ? max : value; + return (value < 0 ? 0 : (value > max ? max : value)); } + function PDFImage(xref, res, image, inline, smask, mask, isMask) { this.image = image; - if (image.getParams) { - // JPX/JPEG2000 streams directly contain bits per component - // and color space mode information. - warn('get params from actual stream'); - // var bits = ... - // var colorspace = ... + var dict = image.dict; + if (dict.has('Filter')) { + var filter = dict.get('Filter').name; + if (filter === 'JPXDecode') { + var jpxImage = new JpxImage(); + jpxImage.parseImageProperties(image.stream); + image.stream.reset(); + image.bitsPerComponent = jpxImage.bitsPerComponent; + image.numComps = jpxImage.componentsCount; + } else if (filter === 'JBIG2Decode') { + image.bitsPerComponent = 1; + image.numComps = 1; + } } // TODO cache rendered images? - var dict = image.dict; this.width = dict.get('Width', 'W'); this.height = dict.get('Height', 'H'); @@ -28833,8 +30747,21 @@ var PDFImage = (function PDFImageClosure() { if (!this.imageMask) { var colorSpace = dict.get('ColorSpace', 'CS'); if (!colorSpace) { - warn('JPX images (which don"t require color spaces'); - colorSpace = Name.get('DeviceRGB'); + info('JPX images (which do not require color spaces)'); + switch (image.numComps) { + case 1: + colorSpace = Name.get('DeviceGray'); + break; + case 3: + colorSpace = Name.get('DeviceRGB'); + break; + case 4: + colorSpace = Name.get('DeviceCMYK'); + break; + default: + error('JPX images with ' + this.numComps + + ' color components not supported.'); + } } this.colorSpace = ColorSpace.parse(colorSpace, xref, res); this.numComps = this.colorSpace.numComps; @@ -28870,51 +30797,47 @@ var PDFImage = (function PDFImageClosure() { } } /** - * Handles processing of image data and calls the callback with an argument - * of a PDFImage when the image is ready to be used. + * Handles processing of image data and returns the Promise that is resolved + * with a PDFImage when the image is ready to be used. */ - PDFImage.buildImage = function PDFImage_buildImage(callback, handler, xref, + PDFImage.buildImage = function PDFImage_buildImage(handler, xref, res, image, inline) { - var imageDataPromise = new LegacyPromise(); - var smaskPromise = new LegacyPromise(); - var maskPromise = new LegacyPromise(); - // The image data and smask data may not be ready yet, wait till both are - // resolved. - Promise.all([imageDataPromise, smaskPromise, maskPromise]).then( - function(results) { - var imageData = results[0], smaskData = results[1], maskData = results[2]; - var image = new PDFImage(xref, res, imageData, inline, smaskData, - maskData); - callback(image); - }); - - handleImageData(handler, xref, res, image, imageDataPromise); + var imagePromise = handleImageData(handler, xref, res, image); + var smaskPromise; + var maskPromise; var smask = image.dict.get('SMask'); var mask = image.dict.get('Mask'); if (smask) { - handleImageData(handler, xref, res, smask, smaskPromise); - maskPromise.resolve(null); + smaskPromise = handleImageData(handler, xref, res, smask); + maskPromise = Promise.resolve(null); } else { - smaskPromise.resolve(null); + smaskPromise = Promise.resolve(null); if (mask) { if (isStream(mask)) { - handleImageData(handler, xref, res, mask, maskPromise); + maskPromise = handleImageData(handler, xref, res, mask); } else if (isArray(mask)) { - maskPromise.resolve(mask); + maskPromise = Promise.resolve(mask); } else { warn('Unsupported mask format.'); - maskPromise.resolve(null); + maskPromise = Promise.resolve(null); } } else { - maskPromise.resolve(null); + maskPromise = Promise.resolve(null); } } + return Promise.all([imagePromise, smaskPromise, maskPromise]).then( + function(results) { + var imageData = results[0]; + var smaskData = results[1]; + var maskData = results[2]; + return new PDFImage(xref, res, imageData, inline, smaskData, maskData); + }); }; /** - * Resize an image using the nearest neighbor algorithm. Currently only + * Resize an image using the nearest neighbor algorithm. Currently only * supports one and three component images. * @param {TypedArray} pixels The original image with one component. * @param {Number} bpc Number of bits per component. @@ -28923,30 +30846,50 @@ var PDFImage = (function PDFImageClosure() { * @param {Number} h1 Original height. * @param {Number} w2 New width. * @param {Number} h2 New height. + * @param {TypedArray} dest (Optional) The destination buffer. + * @param {Number} alpha01 (Optional) Size reserved for the alpha channel. * @return {TypedArray} Resized image data. */ PDFImage.resize = function PDFImage_resize(pixels, bpc, components, - w1, h1, w2, h2) { + w1, h1, w2, h2, dest, alpha01) { + + if (components !== 1 && components !== 3) { + error('Unsupported component count for resizing.'); + } + var length = w2 * h2 * components; - var temp = bpc <= 8 ? new Uint8Array(length) : - bpc <= 16 ? new Uint16Array(length) : new Uint32Array(length); + var temp = dest ? dest : (bpc <= 8 ? new Uint8Array(length) : + (bpc <= 16 ? new Uint16Array(length) : new Uint32Array(length))); var xRatio = w1 / w2; var yRatio = h1 / h2; - var px, py, newIndex, oldIndex; - for (var i = 0; i < h2; i++) { - for (var j = 0; j < w2; j++) { - px = Math.floor(j * xRatio); - py = Math.floor(i * yRatio); - newIndex = (i * w2) + j; - oldIndex = ((py * w1) + px); - if (components === 1) { - temp[newIndex] = pixels[oldIndex]; - } else if (components === 3) { - newIndex *= 3; - oldIndex *= 3; - temp[newIndex] = pixels[oldIndex]; - temp[newIndex + 1] = pixels[oldIndex + 1]; - temp[newIndex + 2] = pixels[oldIndex + 2]; + var i, j, py, newIndex = 0, oldIndex; + var xScaled = new Uint16Array(w2); + var w1Scanline = w1 * components; + if (alpha01 !== 1) { + alpha01 = 0; + } + + for (j = 0; j < w2; j++) { + xScaled[j] = Math.floor(j * xRatio) * components; + } + + if (components === 1) { + for (i = 0; i < h2; i++) { + py = Math.floor(i * yRatio) * w1Scanline; + for (j = 0; j < w2; j++) { + oldIndex = py + xScaled[j]; + temp[newIndex++] = pixels[oldIndex]; + } + } + } else if (components === 3) { + for (i = 0; i < h2; i++) { + py = Math.floor(i * yRatio) * w1Scanline; + for (j = 0; j < w2; j++) { + oldIndex = py + xScaled[j]; + temp[newIndex++] = pixels[oldIndex++]; + temp[newIndex++] = pixels[oldIndex++]; + temp[newIndex++] = pixels[oldIndex++]; + newIndex += alpha01; } } } @@ -28984,37 +30927,39 @@ var PDFImage = (function PDFImageClosure() { this.smask && this.smask.width || 0, this.mask && this.mask.width || 0); }, + get drawHeight() { return Math.max(this.height, this.smask && this.smask.height || 0, this.mask && this.mask.height || 0); }, + decodeBuffer: function PDFImage_decodeBuffer(buffer) { var bpc = this.bpc; - var decodeMap = this.decode; var numComps = this.numComps; - var decodeAddends, decodeCoefficients; var decodeAddends = this.decodeAddends; var decodeCoefficients = this.decodeCoefficients; var max = (1 << bpc) - 1; + var i, ii; if (bpc === 1) { // If the buffer needed decode that means it just needs to be inverted. - for (var i = 0, ii = buffer.length; i < ii; i++) { + for (i = 0, ii = buffer.length; i < ii; i++) { buffer[i] = +!(buffer[i]); } return; } var index = 0; - for (var i = 0, ii = this.width * this.height; i < ii; i++) { + for (i = 0, ii = this.width * this.height; i < ii; i++) { for (var j = 0; j < numComps; j++) { buffer[index] = decodeAndClamp(buffer[index], decodeAddends[j], - decodeCoefficients[j], max); + decodeCoefficients[j], max); index++; } } }, + getComponents: function PDFImage_getComponents(buffer) { var bpc = this.bpc; @@ -29029,15 +30974,16 @@ var PDFImage = (function PDFImageClosure() { var length = width * height * numComps; var bufferPos = 0; - var output = bpc <= 8 ? new Uint8Array(length) : - bpc <= 16 ? new Uint16Array(length) : new Uint32Array(length); + var output = (bpc <= 8 ? new Uint8Array(length) : + (bpc <= 16 ? new Uint16Array(length) : new Uint32Array(length))); var rowComps = width * numComps; var max = (1 << bpc) - 1; + var i = 0, ii, buf; if (bpc === 1) { // Optimization for reading 1 bpc images. - var i = 0, buf, mask, loop1End, loop2End; + var mask, loop1End, loop2End; for (var j = 0; j < height; j++) { loop1End = i + (rowComps & ~7); loop2End = i + rowComps; @@ -29068,8 +31014,9 @@ var PDFImage = (function PDFImageClosure() { } } else { // The general case that handles all other bpc values. - var bits = 0, buf = 0; - for (var i = 0, ii = length; i < ii; ++i) { + var bits = 0; + buf = 0; + for (i = 0, ii = length; i < ii; ++i) { if (i % rowComps === 0) { buf = 0; bits = 0; @@ -29082,22 +31029,23 @@ var PDFImage = (function PDFImageClosure() { var remainingBits = bits - bpc; var value = buf >> remainingBits; - output[i] = value < 0 ? 0 : value > max ? max : value; + output[i] = (value < 0 ? 0 : (value > max ? max : value)); buf = buf & ((1 << remainingBits) - 1); bits = remainingBits; } } return output; }, + fillOpacity: function PDFImage_fillOpacity(rgbaBuf, width, height, actualHeight, image) { var smask = this.smask; var mask = this.mask; - var alphaBuf; + var alphaBuf, sw, sh, i, ii, j; if (smask) { - var sw = smask.width; - var sh = smask.height; + sw = smask.width; + sh = smask.height; alphaBuf = new Uint8Array(sw * sh); smask.fillGrayBuffer(alphaBuf); if (sw != width || sh != height) { @@ -29106,14 +31054,14 @@ var PDFImage = (function PDFImageClosure() { } } else if (mask) { if (mask instanceof PDFImage) { - var sw = mask.width; - var sh = mask.height; + sw = mask.width; + sh = mask.height; alphaBuf = new Uint8Array(sw * sh); mask.numComps = 1; mask.fillGrayBuffer(alphaBuf); // Need to invert values in rgbaBuf - for (var i = 0, ii = sw * sh; i < ii; ++i) { + for (i = 0, ii = sw * sh; i < ii; ++i) { alphaBuf[i] = 255 - alphaBuf[i]; } @@ -29126,10 +31074,10 @@ var PDFImage = (function PDFImageClosure() { // then they should be painted. alphaBuf = new Uint8Array(width * height); var numComps = this.numComps; - for (var i = 0, ii = width * height; i < ii; ++i) { + for (i = 0, ii = width * height; i < ii; ++i) { var opacity = 0; var imageOffset = i * numComps; - for (var j = 0; j < numComps; ++j) { + for (j = 0; j < numComps; ++j) { var color = image[imageOffset + j]; var maskOffset = j * 2; if (color < mask[maskOffset] || color > mask[maskOffset + 1]) { @@ -29145,48 +31093,52 @@ var PDFImage = (function PDFImageClosure() { } if (alphaBuf) { - for (var i = 0, j = 3, ii = width * actualHeight; i < ii; ++i, j += 4) { + for (i = 0, j = 3, ii = width * actualHeight; i < ii; ++i, j += 4) { rgbaBuf[j] = alphaBuf[i]; } } else { // No mask. - for (var i = 0, j = 3, ii = width * actualHeight; i < ii; ++i, j += 4) { + for (i = 0, j = 3, ii = width * actualHeight; i < ii; ++i, j += 4) { rgbaBuf[j] = 255; } } }, + undoPreblend: function PDFImage_undoPreblend(buffer, width, height) { var matte = this.smask && this.smask.matte; if (!matte) { return; } - - function clamp(value) { - return (value < 0 ? 0 : value > 255 ? 255 : value) | 0; - } - var matteRgb = this.colorSpace.getRgb(matte, 0); + var matteR = matteRgb[0]; + var matteG = matteRgb[1]; + var matteB = matteRgb[2]; var length = width * height * 4; + var r, g, b; for (var i = 0; i < length; i += 4) { var alpha = buffer[i + 3]; if (alpha === 0) { // according formula we have to get Infinity in all components - // making it as white (tipical paper color) should be okay + // making it white (typical paper color) should be okay buffer[i] = 255; buffer[i + 1] = 255; buffer[i + 2] = 255; continue; } var k = 255 / alpha; - buffer[i] = clamp((buffer[i] - matteRgb[0]) * k + matteRgb[0]); - buffer[i + 1] = clamp((buffer[i + 1] - matteRgb[1]) * k + matteRgb[1]); - buffer[i + 2] = clamp((buffer[i + 2] - matteRgb[2]) * k + matteRgb[2]); + r = (buffer[i] - matteR) * k + matteR; + g = (buffer[i + 1] - matteG) * k + matteG; + b = (buffer[i + 2] - matteB) * k + matteB; + buffer[i] = r <= 0 ? 0 : r >= 255 ? 255 : r | 0; + buffer[i + 1] = g <= 0 ? 0 : g >= 255 ? 255 : g | 0; + buffer[i + 2] = b <= 0 ? 0 : b >= 255 ? 255 : b | 0; } }, + createImageData: function PDFImage_createImageData(forceRGBA) { var drawWidth = this.drawWidth; var drawHeight = this.drawHeight; - var imgData = { // other fields are filled in below + var imgData = { // other fields are filled in below width: drawWidth, height: drawHeight }; @@ -29198,7 +31150,7 @@ var PDFImage = (function PDFImageClosure() { // Rows start at byte boundary. var rowBytes = (originalWidth * numComps * bpc + 7) >> 3; - var imgArray = this.getImageBytes(originalHeight * rowBytes); + var imgArray; if (!forceRGBA) { // If it is a 1-bit-per-pixel grayscale (i.e. black-and-white) image @@ -29218,6 +31170,7 @@ var PDFImage = (function PDFImageClosure() { drawWidth === originalWidth && drawHeight === originalHeight) { imgData.kind = kind; + imgArray = this.getImageBytes(originalHeight * rowBytes); // If imgArray came from a DecodeStream, we're safe to transfer it // (and thus neuter it) because it will constitute the entire // DecodeStream's data. But if it came from a Stream, we need to @@ -29233,7 +31186,14 @@ var PDFImage = (function PDFImageClosure() { return imgData; } } + if (this.image instanceof JpegStream) { + imgData.kind = ImageKind.RGB_24BPP; + imgData.data = this.getImageBytes(originalHeight * rowBytes, + drawWidth, drawHeight); + return imgData; + } + imgArray = this.getImageBytes(originalHeight * rowBytes); // imgArray can be incomplete (e.g. after CCITT fax encoding). var actualHeight = 0 | (imgArray.length / rowBytes * drawHeight / originalHeight); @@ -29271,32 +31231,35 @@ var PDFImage = (function PDFImageClosure() { return imgData; }, + fillGrayBuffer: function PDFImage_fillGrayBuffer(buffer) { var numComps = this.numComps; - if (numComps != 1) + if (numComps != 1) { error('Reading gray scale from a color image: ' + numComps); + } var width = this.width; var height = this.height; var bpc = this.bpc; - // rows start at byte boundary; + // rows start at byte boundary var rowBytes = (width * numComps * bpc + 7) >> 3; var imgArray = this.getImageBytes(height * rowBytes); var comps = this.getComponents(imgArray); + var i, length; if (bpc === 1) { // inline decoding (= inversion) for 1 bpc images - var length = width * height; + length = width * height; if (this.needsDecode) { - // invert and scale to {0, 255} - for (var i = 0; i < length; ++i) { + // invert and scale to {0, 255} + for (i = 0; i < length; ++i) { buffer[i] = (comps[i] - 1) & 255; } } else { // scale to {0, 255} - for (var i = 0; i < length; ++i) { + for (i = 0; i < length; ++i) { buffer[i] = (-comps[i]) & 255; } } @@ -29306,15 +31269,19 @@ var PDFImage = (function PDFImageClosure() { if (this.needsDecode) { this.decodeBuffer(comps); } - var length = width * height; + length = width * height; // we aren't using a colorspace so we need to scale the value var scale = 255 / ((1 << bpc) - 1); - for (var i = 0; i < length; ++i) { + for (i = 0; i < length; ++i) { buffer[i] = (scale * comps[i]) | 0; } }, - getImageBytes: function PDFImage_getImageBytes(length) { + + getImageBytes: function PDFImage_getImageBytes(length, + drawWidth, drawHeight) { this.image.reset(); + this.image.drawWidth = drawWidth; + this.image.drawHeight = drawHeight; return this.image.getBytes(length); } }; @@ -32269,7 +34236,7 @@ var Metrics = { var EOF = {}; function isEOF(v) { - return v == EOF; + return (v === EOF); } var Parser = (function ParserClosure() { @@ -32300,51 +34267,58 @@ var Parser = (function ParserClosure() { } }, getObj: function Parser_getObj(cipherTransform) { - if (isCmd(this.buf1, 'BI')) { // inline image - this.shift(); - return this.makeInlineImage(cipherTransform); - } - if (isCmd(this.buf1, '[')) { // array - this.shift(); - var array = []; - while (!isCmd(this.buf1, ']') && !isEOF(this.buf1)) - array.push(this.getObj(cipherTransform)); - if (isEOF(this.buf1)) - error('End of file inside array'); - this.shift(); - return array; - } - if (isCmd(this.buf1, '<<')) { // dictionary or stream - this.shift(); - var dict = new Dict(this.xref); - while (!isCmd(this.buf1, '>>') && !isEOF(this.buf1)) { - if (!isName(this.buf1)) { - info('Malformed dictionary, key must be a name object'); + var buf1 = this.buf1; + this.shift(); + + if (buf1 instanceof Cmd) { + switch (buf1.cmd) { + case 'BI': // inline image + return this.makeInlineImage(cipherTransform); + case '[': // array + var array = []; + while (!isCmd(this.buf1, ']') && !isEOF(this.buf1)) { + array.push(this.getObj(cipherTransform)); + } + if (isEOF(this.buf1)) { + error('End of file inside array'); + } this.shift(); - continue; - } + return array; + case '<<': // dictionary or stream + var dict = new Dict(this.xref); + while (!isCmd(this.buf1, '>>') && !isEOF(this.buf1)) { + if (!isName(this.buf1)) { + info('Malformed dictionary: key must be a name object'); + this.shift(); + continue; + } - var key = this.buf1.name; - this.shift(); - if (isEOF(this.buf1)) - break; - dict.set(key, this.getObj(cipherTransform)); - } - if (isEOF(this.buf1)) - error('End of file inside dictionary'); + var key = this.buf1.name; + this.shift(); + if (isEOF(this.buf1)) { + break; + } + dict.set(key, this.getObj(cipherTransform)); + } + if (isEOF(this.buf1)) { + error('End of file inside dictionary'); + } - // stream objects are not allowed inside content streams or - // object streams - if (isCmd(this.buf2, 'stream')) { - return this.allowStreams ? - this.makeStream(dict, cipherTransform) : dict; + // Stream objects are not allowed inside content streams or + // object streams. + if (isCmd(this.buf2, 'stream')) { + return (this.allowStreams ? + this.makeStream(dict, cipherTransform) : dict); + } + this.shift(); + return dict; + default: // simple object + return buf1; } - this.shift(); - return dict; } - if (isInt(this.buf1)) { // indirect reference or integer - var num = this.buf1; - this.shift(); + + if (isInt(buf1)) { // indirect reference or integer + var num = buf1; if (isInt(this.buf1) && isCmd(this.buf2, 'R')) { var ref = new Ref(num, this.buf1); this.shift(); @@ -32353,33 +34327,34 @@ var Parser = (function ParserClosure() { } return num; } - if (isString(this.buf1)) { // string - var str = this.buf1; - this.shift(); - if (cipherTransform) + + if (isString(buf1)) { // string + var str = buf1; + if (cipherTransform) { str = cipherTransform.decryptString(str); + } return str; } // simple object - var obj = this.buf1; - this.shift(); - return obj; + return buf1; }, makeInlineImage: function Parser_makeInlineImage(cipherTransform) { var lexer = this.lexer; var stream = lexer.stream; // parse dictionary - var dict = new Dict(); + var dict = new Dict(null); while (!isCmd(this.buf1, 'ID') && !isEOF(this.buf1)) { - if (!isName(this.buf1)) + if (!isName(this.buf1)) { error('Dictionary key must be a name object'); + } var key = this.buf1.name; this.shift(); - if (isEOF(this.buf1)) + if (isEOF(this.buf1)) { break; + } dict.set(key, this.getObj(cipherTransform)); } @@ -32403,13 +34378,13 @@ var Parser = (function ParserClosure() { break; // some binary stuff found, resetting the state } } - state = state === 3 ? 4 : 0; + state = (state === 3 ? 4 : 0); break; case 0x45: state = 2; break; case 0x49: - state = state === 2 ? 3 : 0; + state = (state === 2 ? 3 : 0); break; default: state = 0; @@ -32430,7 +34405,7 @@ var Parser = (function ParserClosure() { var a = 1; var b = 0; - for (var i = 0, ii = imageBytes.length; i < ii; ++i) { + for (i = 0, ii = imageBytes.length; i < ii; ++i) { a = (a + (imageBytes[i] & 0xff)) % 65521; b = (b + a) % 65521; } @@ -32469,7 +34444,7 @@ var Parser = (function ParserClosure() { }, fetchIfRef: function Parser_fetchIfRef(obj) { // not relying on the xref.fetchIfRef -- xref might not be set - return isRef(obj) ? this.xref.fetch(obj) : obj; + return (isRef(obj) ? this.xref.fetch(obj) : obj); }, makeStream: function Parser_makeStream(dict, cipherTransform) { var lexer = this.lexer; @@ -32499,11 +34474,11 @@ var Parser = (function ParserClosure() { var ENDSTREAM_SIGNATURE_LENGTH = 9; var ENDSTREAM_SIGNATURE = [0x65, 0x6E, 0x64, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6D]; - var skipped = 0, found = false; + var skipped = 0, found = false, i, j; while (stream.pos < stream.end) { var scanBytes = stream.peekBytes(SCAN_BLOCK_SIZE); var scanLength = scanBytes.length - ENDSTREAM_SIGNATURE_LENGTH; - var found = false, i, j; + found = false; for (i = 0, j = 0; i < scanLength; i++) { var b = scanBytes[i]; if (b !== ENDSTREAM_SIGNATURE[j]) { @@ -32538,8 +34513,9 @@ var Parser = (function ParserClosure() { this.shift(); // 'endstream' stream = stream.makeSubStream(pos, length, dict); - if (cipherTransform) + if (cipherTransform) { stream = cipherTransform.createStream(stream, length); + } stream = this.filter(stream, dict, length); stream.dict = dict; return stream; @@ -32547,8 +34523,9 @@ var Parser = (function ParserClosure() { filter: function Parser_filter(stream, dict, length) { var filter = this.fetchIfRef(dict.get('Filter', 'F')); var params = this.fetchIfRef(dict.get('DecodeParms', 'DP')); - if (isName(filter)) + if (isName(filter)) { return this.makeFilter(stream, filter.name, length, params); + } var maybeLength = length; if (isArray(filter)) { @@ -32556,12 +34533,14 @@ var Parser = (function ParserClosure() { var paramsArray = params; for (var i = 0, ii = filterArray.length; i < ii; ++i) { filter = filterArray[i]; - if (!isName(filter)) + if (!isName(filter)) { error('Bad filter name: ' + filter); + } params = null; - if (isArray(paramsArray) && (i in paramsArray)) + if (isArray(paramsArray) && (i in paramsArray)) { params = paramsArray[i]; + } stream = this.makeFilter(stream, filter.name, maybeLength, params); // after the first stream the length variable is invalid maybeLength = null; @@ -32583,11 +34562,12 @@ var Parser = (function ParserClosure() { if (name == 'LZWDecode' || name == 'LZW') { var earlyChange = 1; if (params) { - if (params.has('EarlyChange')) + if (params.has('EarlyChange')) { earlyChange = params.get('EarlyChange'); + } return new PredictorStream( new LZWStream(stream, maybeLength, earlyChange), - maybeLength, params); + maybeLength, params); } return new LZWStream(stream, maybeLength, earlyChange); } @@ -32643,29 +34623,29 @@ var Lexer = (function LexerClosure() { } Lexer.isSpace = function Lexer_isSpace(ch) { - // space is one of the following characters: SPACE, TAB, CR, or LF - return ch === 0x20 || ch === 0x09 || ch === 0x0D || ch === 0x0A; + // Space is one of the following characters: SPACE, TAB, CR or LF. + return (ch === 0x20 || ch === 0x09 || ch === 0x0D || ch === 0x0A); }; - // A '1' in this array means the character is white space. A '1' or + // A '1' in this array means the character is white space. A '1' or // '2' means the character ends a name or command. var specialChars = [ - 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, // 0x - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1x - 1, 0, 0, 0, 0, 2, 0, 0, 2, 2, 0, 0, 0, 0, 0, 2, // 2x - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, // 3x - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4x - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, // 5x - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6x - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, // 7x - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8x - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9x - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ax - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // bx - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // cx - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // dx - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ex - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // fx + 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, // 0x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1x + 1, 0, 0, 0, 0, 2, 0, 0, 2, 2, 0, 0, 0, 0, 0, 2, // 2x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, // 3x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, // 5x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, // 7x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ax + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // bx + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // cx + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // dx + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ex + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // fx ]; function toHexDigit(ch) { @@ -32690,10 +34670,8 @@ var Lexer = (function LexerClosure() { var ch = this.currentChar; var eNotation = false; var divideBy = 0; // different from 0 if it's a floating point value - var sign = 1; - if (ch === 0x2D) { // '-' sign = -1; ch = this.nextChar(); @@ -32704,7 +34682,6 @@ var Lexer = (function LexerClosure() { divideBy = 10; ch = this.nextChar(); } - if (ch < 0x30 || ch > 0x39) { // '0' - '9' error('Invalid number: ' + String.fromCharCode(ch)); return 0; @@ -32739,7 +34716,6 @@ var Lexer = (function LexerClosure() { } else if (ch === 0x45 || ch === 0x65) { // 'E', 'e' // 'E' can be either a scientific notation or the beginning of a new // operator - var hasE = true; ch = this.peekChar(); if (ch === 0x2B || ch === 0x2D) { // '+', '-' powerValueSign = (ch === 0x2D) ? -1 : 1; @@ -32829,10 +34805,14 @@ var Lexer = (function LexerClosure() { x = (x << 3) + (ch & 0x0F); } } - strBuf.push(String.fromCharCode(x)); break; - case 0x0A: case 0x0D: // LF, CR + case 0x0D: // CR + if (this.peekChar() === 0x0A) { // LF + this.nextChar(); + } + break; + case 0x0A: // LF break; default: strBuf.push(String.fromCharCode(ch)); @@ -32862,8 +34842,9 @@ var Lexer = (function LexerClosure() { var x = toHexDigit(ch); if (x != -1) { var x2 = toHexDigit(this.nextChar()); - if (x2 == -1) + if (x2 === -1) { error('Illegal digit in hex char in name: ' + x2); + } strBuf.push(String.fromCharCode((x << 4) | x2)); } else { strBuf.push('#', String.fromCharCode(ch)); @@ -32927,8 +34908,9 @@ var Lexer = (function LexerClosure() { return EOF; } if (comment) { - if (ch === 0x0A || ch == 0x0D) // LF, CR + if (ch === 0x0A || ch === 0x0D) { // LF, CR comment = false; + } } else if (ch === 0x25) { // '%' comment = true; } else if (specialChars[ch] !== 1) { @@ -32993,21 +34975,24 @@ var Lexer = (function LexerClosure() { if (knownCommandFound && !(possibleCommand in knownCommands)) { break; } - if (str.length == 128) + if (str.length === 128) { error('Command token too long: ' + str.length); + } str = possibleCommand; knownCommandFound = knownCommands && (str in knownCommands); } - if (str == 'true') + if (str === 'true') { return true; - if (str == 'false') + } + if (str === 'false') { return false; - if (str == 'null') + } + if (str === 'null') { return null; + } return Cmd.get(str); }, skipToNextLine: function Lexer_skipToNextLine() { - var stream = this.stream; var ch = this.currentChar; while (ch >= 0) { if (ch === 0x0D) { // CR @@ -33038,8 +35023,9 @@ var Linearization = (function LinearizationClosure() { if (isInt(obj1) && isInt(obj2) && isCmd(obj3, 'obj') && isDict(this.linDict)) { var obj = this.linDict.get('Linearized'); - if (!(isNum(obj) && obj > 0)) + if (!(isNum(obj) && obj > 0)) { this.linDict = null; + } } } @@ -33047,9 +35033,7 @@ var Linearization = (function LinearizationClosure() { getInt: function Linearization_getInt(name) { var linDict = this.linDict; var obj; - if (isDict(linDict) && - isInt(obj = linDict.get(name)) && - obj > 0) { + if (isDict(linDict) && isInt(obj = linDict.get(name)) && obj > 0) { return obj; } error('"' + name + '" field in linearization table is invalid'); @@ -33057,18 +35041,16 @@ var Linearization = (function LinearizationClosure() { getHint: function Linearization_getHint(index) { var linDict = this.linDict; var obj1, obj2; - if (isDict(linDict) && - isArray(obj1 = linDict.get('H')) && - obj1.length >= 2 && - isInt(obj2 = obj1[index]) && - obj2 > 0) { + if (isDict(linDict) && isArray(obj1 = linDict.get('H')) && + obj1.length >= 2 && isInt(obj2 = obj1[index]) && obj2 > 0) { return obj2; } error('Hints table in linearization table is invalid: ' + index); }, get length() { - if (!isDict(this.linDict)) + if (!isDict(this.linDict)) { return 0; + } return this.getInt('L'); }, get hintsOffset() { @@ -33230,7 +35212,6 @@ var PostScriptLexer = (function PostScriptLexerClosure() { return (this.currentChar = this.stream.getByte()); }, getToken: function PostScriptLexer_getToken() { - var s = ''; var comment = false; var ch = this.currentChar; @@ -33303,8 +35284,8 @@ var PostScriptLexer = (function PostScriptLexerClosure() { var Stream = (function StreamClosure() { function Stream(arrayBuffer, start, length, dict) { - this.bytes = arrayBuffer instanceof Uint8Array ? arrayBuffer : - new Uint8Array(arrayBuffer); + this.bytes = (arrayBuffer instanceof Uint8Array ? + arrayBuffer : new Uint8Array(arrayBuffer)); this.start = start || 0; this.pos = this.start; this.end = (start + length) || this.bytes.length; @@ -33317,11 +35298,27 @@ var Stream = (function StreamClosure() { get length() { return this.end - this.start; }, + get isEmpty() { + return this.length === 0; + }, getByte: function Stream_getByte() { - if (this.pos >= this.end) + if (this.pos >= this.end) { return -1; + } return this.bytes[this.pos++]; }, + getUint16: function Stream_getUint16() { + var b0 = this.getByte(); + var b1 = this.getByte(); + return (b0 << 8) + b1; + }, + getInt32: function Stream_getInt32() { + var b0 = this.getByte(); + var b1 = this.getByte(); + var b2 = this.getByte(); + var b3 = this.getByte(); + return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3; + }, // returns subarray of original buffer // should only be read getBytes: function Stream_getBytes(length) { @@ -33329,13 +35326,13 @@ var Stream = (function StreamClosure() { var pos = this.pos; var strEnd = this.end; - if (!length) + if (!length) { return bytes.subarray(pos, strEnd); - + } var end = pos + length; - if (end > strEnd) + if (end > strEnd) { end = strEnd; - + } this.pos = end; return bytes.subarray(pos, end); }, @@ -33345,8 +35342,9 @@ var Stream = (function StreamClosure() { return bytes; }, skip: function Stream_skip(n) { - if (!n) + if (!n) { n = 1; + } this.pos += n; }, reset: function Stream_reset() { @@ -33368,8 +35366,9 @@ var StringStream = (function StringStreamClosure() { function StringStream(str) { var length = str.length; var bytes = new Uint8Array(length); - for (var n = 0; n < length; ++n) + for (var n = 0; n < length; ++n) { bytes[n] = str.charCodeAt(n); + } Stream.call(this, bytes); } @@ -33395,6 +35394,12 @@ var DecodeStream = (function DecodeStreamClosure() { } DecodeStream.prototype = { + get isEmpty() { + while (!this.eof && this.bufferLength === 0) { + this.readBlock(); + } + return this.bufferLength === 0; + }, ensureBuffer: function DecodeStream_ensureBuffer(requested) { var buffer = this.buffer; var current; @@ -33411,20 +35416,33 @@ var DecodeStream = (function DecodeStreamClosure() { size *= 2; } var buffer2 = new Uint8Array(size); - for (var i = 0; i < current; ++i) { - buffer2[i] = buffer[i]; + if (buffer) { + buffer2.set(buffer); } return (this.buffer = buffer2); }, getByte: function DecodeStream_getByte() { var pos = this.pos; while (this.bufferLength <= pos) { - if (this.eof) + if (this.eof) { return -1; + } this.readBlock(); } return this.buffer[this.pos++]; }, + getUint16: function DecodeStream_getUint16() { + var b0 = this.getByte(); + var b1 = this.getByte(); + return (b0 << 8) + b1; + }, + getInt32: function DecodeStream_getInt32() { + var b0 = this.getByte(); + var b1 = this.getByte(); + var b2 = this.getByte(); + var b3 = this.getByte(); + return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3; + }, getBytes: function DecodeStream_getBytes(length) { var end, pos = this.pos; @@ -33432,22 +35450,24 @@ var DecodeStream = (function DecodeStreamClosure() { this.ensureBuffer(pos + length); end = pos + length; - while (!this.eof && this.bufferLength < end) + while (!this.eof && this.bufferLength < end) { this.readBlock(); - + } var bufEnd = this.bufferLength; - if (end > bufEnd) + if (end > bufEnd) { end = bufEnd; + } } else { - while (!this.eof) + while (!this.eof) { this.readBlock(); - + } end = this.bufferLength; // checking if bufferLength is still 0 then // the buffer has to be initialized - if (!end) + if (!end) { this.buffer = new Uint8Array(0); + } } this.pos = end; @@ -33460,13 +35480,15 @@ var DecodeStream = (function DecodeStreamClosure() { }, makeSubStream: function DecodeStream_makeSubStream(start, length, dict) { var end = start + length; - while (this.bufferLength <= end && !this.eof) + while (this.bufferLength <= end && !this.eof) { this.readBlock(); + } return new Stream(this.buffer, start, length, dict); }, - skip: function Stream_skip(n) { - if (!n) + skip: function DecodeStream_skip(n) { + if (!n) { n = 1; + } this.pos += n; }, reset: function DecodeStream_reset() { @@ -33492,7 +35514,7 @@ var StreamsSequenceStream = (function StreamsSequenceStreamClosure() { StreamsSequenceStream.prototype = Object.create(DecodeStream.prototype); StreamsSequenceStream.prototype.readBlock = - function streamSequenceStreamReadBlock() { + function streamSequenceStreamReadBlock() { var streams = this.streams; if (streams.length === 0) { @@ -33623,14 +35645,18 @@ var FlateStream = (function FlateStreamClosure() { var cmf = str.getByte(); var flg = str.getByte(); - if (cmf == -1 || flg == -1) + if (cmf == -1 || flg == -1) { error('Invalid header in flate stream: ' + cmf + ', ' + flg); - if ((cmf & 0x0f) != 0x08) + } + if ((cmf & 0x0f) != 0x08) { error('Unknown compression method in flate stream: ' + cmf + ', ' + flg); - if ((((cmf << 8) + flg) % 31) !== 0) + } + if ((((cmf << 8) + flg) % 31) !== 0) { error('Bad FCHECK in flate stream: ' + cmf + ', ' + flg); - if (flg & 0x20) + } + if (flg & 0x20) { error('FDICT bit set in flate stream: ' + cmf + ', ' + flg); + } this.codeSize = 0; this.codeBuf = 0; @@ -33678,8 +35704,9 @@ var FlateStream = (function FlateStreamClosure() { var code = codes[codeBuf & ((1 << maxLen) - 1)]; var codeLen = code >> 16; var codeVal = code & 0xffff; - if (codeSize === 0 || codeSize < codeLen || codeLen === 0) + if (codeSize === 0 || codeSize < codeLen || codeLen === 0) { error('Bad encoding in flate stream'); + } this.codeBuf = (codeBuf >> codeLen); this.codeSize = (codeSize - codeLen); return codeVal; @@ -33691,9 +35718,11 @@ var FlateStream = (function FlateStreamClosure() { // find max code length var maxLen = 0; - for (var i = 0; i < n; ++i) { - if (lengths[i] > maxLen) + var i; + for (i = 0; i < n; ++i) { + if (lengths[i] > maxLen) { maxLen = lengths[i]; + } } // build the table @@ -33707,15 +35736,15 @@ var FlateStream = (function FlateStreamClosure() { // bit-reverse the code var code2 = 0; var t = code; - for (var i = 0; i < len; ++i) { + for (i = 0; i < len; ++i) { code2 = (code2 << 1) | (t & 1); t >>= 1; } // fill the table entries - for (var i = code2; i < size; i += skip) + for (i = code2; i < size; i += skip) { codes[i] = (len << 16) | val; - + } ++code; } } @@ -33725,11 +35754,13 @@ var FlateStream = (function FlateStreamClosure() { }; FlateStream.prototype.readBlock = function FlateStream_readBlock() { + var buffer, len; var str = this.str; // read block header var hdr = this.getBits(3); - if (hdr & 1) + if (hdr & 1) { this.eof = true; + } hdr >>= 1; if (hdr === 0) { // uncompressed block @@ -33761,7 +35792,7 @@ var FlateStream = (function FlateStreamClosure() { this.codeSize = 0; var bufferLength = this.bufferLength; - var buffer = this.ensureBuffer(bufferLength + blockLen); + buffer = this.ensureBuffer(bufferLength + blockLen); var end = bufferLength + blockLen; this.bufferLength = end; if (blockLen === 0) { @@ -33793,31 +35824,35 @@ var FlateStream = (function FlateStreamClosure() { // build the code lengths code table var codeLenCodeLengths = new Uint8Array(codeLenCodeMap.length); - for (var i = 0; i < numCodeLenCodes; ++i) + var i; + for (i = 0; i < numCodeLenCodes; ++i) { codeLenCodeLengths[codeLenCodeMap[i]] = this.getBits(3); + } var codeLenCodeTab = this.generateHuffmanTable(codeLenCodeLengths); // build the literal and distance code tables - var len = 0; - var i = 0; + len = 0; + i = 0; var codes = numLitCodes + numDistCodes; var codeLengths = new Uint8Array(codes); + var bitsLength, bitsOffset, what; while (i < codes) { var code = this.getCode(codeLenCodeTab); if (code == 16) { - var bitsLength = 2, bitsOffset = 3, what = len; + bitsLength = 2; bitsOffset = 3; what = len; } else if (code == 17) { - var bitsLength = 3, bitsOffset = 3, what = (len = 0); + bitsLength = 3; bitsOffset = 3; what = (len = 0); } else if (code == 18) { - var bitsLength = 7, bitsOffset = 11, what = (len = 0); + bitsLength = 7; bitsOffset = 11; what = (len = 0); } else { codeLengths[i++] = len = code; continue; } var repeatLength = this.getBits(bitsLength) + bitsOffset; - while (repeatLength-- > 0) + while (repeatLength-- > 0) { codeLengths[i++] = what; + } } litCodeTable = @@ -33828,7 +35863,7 @@ var FlateStream = (function FlateStreamClosure() { error('Unknown block type in flate stream'); } - var buffer = this.buffer; + buffer = this.buffer; var limit = buffer ? buffer.length : 0; var pos = this.bufferLength; while (true) { @@ -33848,21 +35883,24 @@ var FlateStream = (function FlateStreamClosure() { code1 -= 257; code1 = lengthDecode[code1]; var code2 = code1 >> 16; - if (code2 > 0) + if (code2 > 0) { code2 = this.getBits(code2); - var len = (code1 & 0xffff) + code2; + } + len = (code1 & 0xffff) + code2; code1 = this.getCode(distCodeTable); code1 = distDecode[code1]; code2 = code1 >> 16; - if (code2 > 0) + if (code2 > 0) { code2 = this.getBits(code2); + } var dist = (code1 & 0xffff) + code2; if (pos + len >= limit) { buffer = this.ensureBuffer(pos + len); limit = buffer.length; } - for (var k = 0; k < len; ++k, ++pos) + for (var k = 0; k < len; ++k, ++pos) { buffer[pos] = buffer[pos - dist]; + } } }; @@ -33873,15 +35911,18 @@ var PredictorStream = (function PredictorStreamClosure() { function PredictorStream(str, maybeLength, params) { var predictor = this.predictor = params.get('Predictor') || 1; - if (predictor <= 1) + if (predictor <= 1) { return str; // no prediction - if (predictor !== 2 && (predictor < 10 || predictor > 15)) + } + if (predictor !== 2 && (predictor < 10 || predictor > 15)) { error('Unsupported predictor: ' + predictor); + } - if (predictor === 2) + if (predictor === 2) { this.readBlock = this.readBlockTiff; - else + } else { this.readBlock = this.readBlockPng; + } this.str = str; this.dict = str.dict; @@ -33900,7 +35941,7 @@ var PredictorStream = (function PredictorStreamClosure() { PredictorStream.prototype = Object.create(DecodeStream.prototype); PredictorStream.prototype.readBlockTiff = - function predictorStreamReadBlockTiff() { + function predictorStreamReadBlockTiff() { var rowBytes = this.rowBytes; var bufferLength = this.bufferLength; @@ -33918,9 +35959,10 @@ var PredictorStream = (function PredictorStreamClosure() { var inbuf = 0, outbuf = 0; var inbits = 0, outbits = 0; var pos = bufferLength; + var i; if (bits === 1) { - for (var i = 0; i < rowBytes; ++i) { + for (i = 0; i < rowBytes; ++i) { var c = rawBytes[i]; inbuf = (inbuf << 8) | c; // bitwise addition is exclusive or @@ -33930,8 +35972,9 @@ var PredictorStream = (function PredictorStreamClosure() { inbuf &= 0xFFFF; } } else if (bits === 8) { - for (var i = 0; i < colors; ++i) + for (i = 0; i < colors; ++i) { buffer[pos++] = rawBytes[i]; + } for (; i < rowBytes; ++i) { buffer[pos] = buffer[pos - colors] + rawBytes[i]; pos++; @@ -33941,7 +35984,7 @@ var PredictorStream = (function PredictorStreamClosure() { var bitMask = (1 << bits) - 1; var j = 0, k = bufferLength; var columns = this.columns; - for (var i = 0; i < columns; ++i) { + for (i = 0; i < columns; ++i) { for (var kk = 0; kk < colors; ++kk) { if (inbits < bits) { inbuf = (inbuf << 8) | (rawBytes[j++] & 0xFF); @@ -33960,14 +36003,14 @@ var PredictorStream = (function PredictorStreamClosure() { } if (outbits > 0) { buffer[k++] = (outbuf << (8 - outbits)) + - (inbuf & ((1 << (8 - outbits)) - 1)); + (inbuf & ((1 << (8 - outbits)) - 1)); } } this.bufferLength += rowBytes; }; PredictorStream.prototype.readBlockPng = - function predictorStreamReadBlockPng() { + function predictorStreamReadBlockPng() { var rowBytes = this.rowBytes; var pixBytes = this.pixBytes; @@ -33983,30 +36026,35 @@ var PredictorStream = (function PredictorStreamClosure() { var buffer = this.ensureBuffer(bufferLength + rowBytes); var prevRow = buffer.subarray(bufferLength - rowBytes, bufferLength); - if (prevRow.length === 0) + if (prevRow.length === 0) { prevRow = new Uint8Array(rowBytes); + } - var j = bufferLength; + var i, j = bufferLength, up, c; switch (predictor) { case 0: - for (var i = 0; i < rowBytes; ++i) + for (i = 0; i < rowBytes; ++i) { buffer[j++] = rawBytes[i]; + } break; case 1: - for (var i = 0; i < pixBytes; ++i) + for (i = 0; i < pixBytes; ++i) { buffer[j++] = rawBytes[i]; + } for (; i < rowBytes; ++i) { buffer[j] = (buffer[j - pixBytes] + rawBytes[i]) & 0xFF; j++; } break; case 2: - for (var i = 0; i < rowBytes; ++i) + for (i = 0; i < rowBytes; ++i) { buffer[j++] = (prevRow[i] + rawBytes[i]) & 0xFF; + } break; case 3: - for (var i = 0; i < pixBytes; ++i) + for (i = 0; i < pixBytes; ++i) { buffer[j++] = (prevRow[i] >> 1) + rawBytes[i]; + } for (; i < rowBytes; ++i) { buffer[j] = (((prevRow[i] + buffer[j - pixBytes]) >> 1) + rawBytes[i]) & 0xFF; @@ -34016,34 +36064,38 @@ var PredictorStream = (function PredictorStreamClosure() { case 4: // we need to save the up left pixels values. the simplest way // is to create a new buffer - for (var i = 0; i < pixBytes; ++i) { - var up = prevRow[i]; - var c = rawBytes[i]; + for (i = 0; i < pixBytes; ++i) { + up = prevRow[i]; + c = rawBytes[i]; buffer[j++] = up + c; } for (; i < rowBytes; ++i) { - var up = prevRow[i]; + up = prevRow[i]; var upLeft = prevRow[i - pixBytes]; var left = buffer[j - pixBytes]; var p = left + up - upLeft; var pa = p - left; - if (pa < 0) + if (pa < 0) { pa = -pa; + } var pb = p - up; - if (pb < 0) + if (pb < 0) { pb = -pb; + } var pc = p - upLeft; - if (pc < 0) + if (pc < 0) { pc = -pc; + } - var c = rawBytes[i]; - if (pa <= pb && pa <= pc) + c = rawBytes[i]; + if (pa <= pb && pa <= pc) { buffer[j++] = left + c; - else if (pb <= pc) + } else if (pb <= pc) { buffer[j++] = up + c; - else + } else { buffer[j++] = upLeft + c; + } } break; default: @@ -34084,16 +36136,17 @@ var JpegStream = (function JpegStreamClosure() { }); JpegStream.prototype.ensureBuffer = function JpegStream_ensureBuffer(req) { - if (this.bufferLength) + if (this.bufferLength) { return; + } try { var jpegImage = new JpegImage(); - if (this.colorTransform != -1) + if (this.colorTransform != -1) { jpegImage.colorTransform = this.colorTransform; + } jpegImage.parse(this.bytes); - var width = jpegImage.width; - var height = jpegImage.height; - var data = jpegImage.getData(width, height); + var data = jpegImage.getData(this.drawWidth, this.drawHeight, + /* forceRGBoutput = */true); this.buffer = data; this.bufferLength = data.length; this.eof = true; @@ -34101,6 +36154,12 @@ var JpegStream = (function JpegStreamClosure() { error('JPEG error: ' + e); } }; + + JpegStream.prototype.getBytes = function JpegStream_getBytes(length) { + this.ensureBuffer(); + return this.buffer; + }; + JpegStream.prototype.getIR = function JpegStream_getIR() { return PDFJS.createObjectURL(this.bytes, 'image/jpeg'); }; @@ -34109,7 +36168,7 @@ var JpegStream = (function JpegStreamClosure() { * further processing such as color space conversions. */ JpegStream.prototype.isNativelySupported = - function JpegStream_isNativelySupported(xref, res) { + function JpegStream_isNativelySupported(xref, res) { var cs = ColorSpace.parse(this.dict.get('ColorSpace', 'CS'), xref, res); return cs.name === 'DeviceGray' || cs.name === 'DeviceRGB'; }; @@ -34117,7 +36176,7 @@ var JpegStream = (function JpegStreamClosure() { * Checks if the image can be decoded by the browser. */ JpegStream.prototype.isNativelyDecodable = - function JpegStream_isNativelyDecodable(xref, res) { + function JpegStream_isNativelyDecodable(xref, res) { var cs = ColorSpace.parse(this.dict.get('ColorSpace', 'CS'), xref, res); var numComps = cs.numComps; return numComps == 1 || numComps == 3; @@ -34150,8 +36209,9 @@ var JpxStream = (function JpxStreamClosure() { }); JpxStream.prototype.ensureBuffer = function JpxStream_ensureBuffer(req) { - if (this.bufferLength) + if (this.bufferLength) { return; + } var jpxImage = new JpxImage(); jpxImage.parse(this.bytes); @@ -34159,75 +36219,35 @@ var JpxStream = (function JpxStreamClosure() { var width = jpxImage.width; var height = jpxImage.height; var componentsCount = jpxImage.componentsCount; - if (componentsCount != 1 && componentsCount != 3 && componentsCount != 4) - error('JPX with ' + componentsCount + ' components is not supported'); - - var data = new Uint8Array(width * height * componentsCount); - - for (var k = 0, kk = jpxImage.tiles.length; k < kk; k++) { - var tileCompoments = jpxImage.tiles[k]; - var tileWidth = tileCompoments[0].width; - var tileHeight = tileCompoments[0].height; - var tileLeft = tileCompoments[0].left; - var tileTop = tileCompoments[0].top; + var tileCount = jpxImage.tiles.length; + if (tileCount === 1) { + this.buffer = jpxImage.tiles[0].items; + } else { + var data = new Uint8Array(width * height * componentsCount); - var dataPosition, sourcePosition, data0, data1, data2, data3, rowFeed; - switch (componentsCount) { - case 1: - data0 = tileCompoments[0].items; + for (var k = 0; k < tileCount; k++) { + var tileComponents = jpxImage.tiles[k]; + var tileWidth = tileComponents.width; + var tileHeight = tileComponents.height; + var tileLeft = tileComponents.left; + var tileTop = tileComponents.top; - dataPosition = width * tileTop + tileLeft; - rowFeed = width - tileWidth; - sourcePosition = 0; - for (var j = 0; j < tileHeight; j++) { - for (var i = 0; i < tileWidth; i++) - data[dataPosition++] = data0[sourcePosition++]; - dataPosition += rowFeed; - } - break; - case 3: - data0 = tileCompoments[0].items; - data1 = tileCompoments[1].items; - data2 = tileCompoments[2].items; - - dataPosition = (width * tileTop + tileLeft) * 3; - rowFeed = (width - tileWidth) * 3; - sourcePosition = 0; - for (var j = 0; j < tileHeight; j++) { - for (var i = 0; i < tileWidth; i++) { - data[dataPosition++] = data0[sourcePosition]; - data[dataPosition++] = data1[sourcePosition]; - data[dataPosition++] = data2[sourcePosition]; - sourcePosition++; - } - dataPosition += rowFeed; - } - break; - case 4: - data0 = tileCompoments[0].items; - data1 = tileCompoments[1].items; - data2 = tileCompoments[2].items; - data3 = tileCompoments[3].items; + var src = tileComponents.items; + var srcPosition = 0; + var dataPosition = (width * tileTop + tileLeft) * componentsCount; + var imgRowSize = width * componentsCount; + var tileRowSize = tileWidth * componentsCount; - dataPosition = (width * tileTop + tileLeft) * 4; - rowFeed = (width - tileWidth) * 4; - sourcePosition = 0; - for (var j = 0; j < tileHeight; j++) { - for (var i = 0; i < tileWidth; i++) { - data[dataPosition++] = data0[sourcePosition]; - data[dataPosition++] = data1[sourcePosition]; - data[dataPosition++] = data2[sourcePosition]; - data[dataPosition++] = data3[sourcePosition]; - sourcePosition++; - } - dataPosition += rowFeed; - } - break; + for (var j = 0; j < tileHeight; j++) { + var rowBytes = src.subarray(srcPosition, srcPosition + tileRowSize); + data.set(rowBytes, dataPosition); + srcPosition += tileRowSize; + dataPosition += imgRowSize; + } } + this.buffer = data; } - - this.buffer = data; - this.bufferLength = data.length; + this.bufferLength = this.buffer.length; this.eof = true; }; @@ -34258,8 +36278,9 @@ var Jbig2Stream = (function Jbig2StreamClosure() { }); Jbig2Stream.prototype.ensureBuffer = function Jbig2Stream_ensureBuffer(req) { - if (this.bufferLength) + if (this.bufferLength) { return; + } var jbig2Image = new Jbig2Image(); @@ -34284,8 +36305,9 @@ var Jbig2Stream = (function Jbig2StreamClosure() { var dataLength = data.length; // JBIG2 had black as 1 and white as 0, inverting the colors - for (var i = 0; i < dataLength; i++) + for (var i = 0; i < dataLength; i++) { data[i] ^= 0xFF; + } this.buffer = data; this.bufferLength = dataLength; @@ -34331,8 +36353,9 @@ var DecryptStream = (function DecryptStreamClosure() { var bufferLength = this.bufferLength; var i, n = chunk.length; var buffer = this.ensureBuffer(bufferLength + n); - for (i = 0; i < n; i++) + for (i = 0; i < n; i++) { buffer[bufferLength++] = chunk[i]; + } this.bufferLength = bufferLength; }; @@ -34373,17 +36396,19 @@ var Ascii85Stream = (function Ascii85StreamClosure() { } var bufferLength = this.bufferLength, buffer; + var i; // special code for z if (c == Z_LOWER_CHAR) { buffer = this.ensureBuffer(bufferLength + 4); - for (var i = 0; i < 4; ++i) + for (i = 0; i < 4; ++i) { buffer[bufferLength + i] = 0; + } this.bufferLength += 4; } else { var input = this.input; input[0] = c; - for (var i = 1; i < 5; ++i) { + for (i = 1; i < 5; ++i) { c = str.getByte(); while (Lexer.isSpace(c)) { c = str.getByte(); @@ -34391,23 +36416,26 @@ var Ascii85Stream = (function Ascii85StreamClosure() { input[i] = c; - if (c === EOF || c == TILDA_CHAR) + if (c === EOF || c == TILDA_CHAR) { break; + } } buffer = this.ensureBuffer(bufferLength + i - 1); this.bufferLength += i - 1; // partial ending; if (i < 5) { - for (; i < 5; ++i) + for (; i < 5; ++i) { input[i] = 0x21 + 84; + } this.eof = true; } var t = 0; - for (var i = 0; i < 5; ++i) + for (i = 0; i < 5; ++i) { t = t * 85 + (input[i] - 0x21); + } - for (var i = 3; i >= 0; --i) { + for (i = 3; i >= 0; --i) { buffer[bufferLength + i] = t & 0xFF; t >>= 8; } @@ -34500,11 +36528,12 @@ var RunLengthStream = (function RunLengthStreamClosure() { return; } + var buffer; var bufferLength = this.bufferLength; var n = repeatHeader[0]; if (n < 128) { // copy n bytes - var buffer = this.ensureBuffer(bufferLength + n + 1); + buffer = this.ensureBuffer(bufferLength + n + 1); buffer[bufferLength++] = repeatHeader[1]; if (n > 0) { var source = this.str.getBytes(n); @@ -34514,9 +36543,10 @@ var RunLengthStream = (function RunLengthStreamClosure() { } else { n = 257 - n; var b = repeatHeader[1]; - var buffer = this.ensureBuffer(bufferLength + n + 1); - for (var i = 0; i < n; i++) + buffer = this.ensureBuffer(bufferLength + n + 1); + for (var i = 0; i < n; i++) { buffer[bufferLength++] = b; + } } this.bufferLength = bufferLength; }; @@ -34956,7 +36986,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { this.str = str; this.dict = str.dict; - params = params || new Dict(); + params = params || Dict.empty; this.encoding = params.get('K') || 0; this.eoline = params.get('EndOfLine') || false; @@ -34964,8 +36994,9 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { this.columns = params.get('Columns') || 1728; this.rows = params.get('Rows') || 0; var eoblock = params.get('EndOfBlock'); - if (eoblock === null || eoblock === undefined) + if (eoblock === null || eoblock === undefined) { eoblock = true; + } this.eoblock = eoblock; this.black = params.get('BlackIs1') || false; @@ -35007,7 +37038,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { }; CCITTFaxStream.prototype.addPixels = - function ccittFaxStreamAddPixels(a1, blackPixels) { + function ccittFaxStreamAddPixels(a1, blackPixels) { var codingLine = this.codingLine; var codingPos = this.codingPos; @@ -35027,7 +37058,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { }; CCITTFaxStream.prototype.addPixelsNeg = - function ccittFaxStreamAddPixelsNeg(a1, blackPixels) { + function ccittFaxStreamAddPixelsNeg(a1, blackPixels) { var codingLine = this.codingLine; var codingPos = this.codingPos; @@ -35037,8 +37068,9 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { this.err = true; a1 = this.columns; } - if ((codingPos & 1) ^ blackPixels) + if ((codingPos & 1) ^ blackPixels) { ++codingPos; + } codingLine[codingPos] = a1; } else if (a1 < codingLine[codingPos]) { @@ -35047,8 +37079,9 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { this.err = true; a1 = 0; } - while (codingPos > 0 && a1 < codingLine[codingPos - 1]) + while (codingPos > 0 && a1 < codingLine[codingPos - 1]) { --codingPos; + } codingLine[codingPos] = a1; } @@ -35060,19 +37093,19 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { var codingLine = this.codingLine; var columns = this.columns; - var refPos, blackPixels, bits; + var refPos, blackPixels, bits, i; if (this.outputBits === 0) { - if (this.eof) + if (this.eof) { return null; - + } this.err = false; var code1, code2, code3; if (this.nextLine2D) { - for (var i = 0; codingLine[i] < columns; ++i) + for (i = 0; codingLine[i] < columns; ++i) { refLine[i] = codingLine[i]; - + } refLine[i++] = columns; refLine[i] = columns; codingLine[0] = 0; @@ -35085,8 +37118,9 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { switch (code1) { case twoDimPass: this.addPixels(refLine[refPos + 1], blackPixels); - if (refLine[refPos + 1] < columns) + if (refLine[refPos + 1] < columns) { refPos += 2; + } break; case twoDimHoriz: code1 = code2 = 0; @@ -35122,8 +37156,9 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { if (codingLine[this.codingPos] < columns) { ++refPos; while (refLine[refPos] <= codingLine[this.codingPos] && - refLine[refPos] < columns) + refLine[refPos] < columns) { refPos += 2; + } } break; case twoDimVertR2: @@ -35143,8 +37178,9 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { if (codingLine[this.codingPos] < columns) { ++refPos; while (refLine[refPos] <= codingLine[this.codingPos] && - refLine[refPos] < columns) + refLine[refPos] < columns) { refPos += 2; + } } break; case twoDimVert0: @@ -35153,48 +37189,54 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { if (codingLine[this.codingPos] < columns) { ++refPos; while (refLine[refPos] <= codingLine[this.codingPos] && - refLine[refPos] < columns) + refLine[refPos] < columns) { refPos += 2; + } } break; case twoDimVertL3: this.addPixelsNeg(refLine[refPos] - 3, blackPixels); blackPixels ^= 1; if (codingLine[this.codingPos] < columns) { - if (refPos > 0) + if (refPos > 0) { --refPos; - else + } else { ++refPos; + } while (refLine[refPos] <= codingLine[this.codingPos] && - refLine[refPos] < columns) + refLine[refPos] < columns) { refPos += 2; + } } break; case twoDimVertL2: this.addPixelsNeg(refLine[refPos] - 2, blackPixels); blackPixels ^= 1; if (codingLine[this.codingPos] < columns) { - if (refPos > 0) + if (refPos > 0) { --refPos; - else + } else { ++refPos; + } while (refLine[refPos] <= codingLine[this.codingPos] && - refLine[refPos] < columns) + refLine[refPos] < columns) { refPos += 2; + } } break; case twoDimVertL1: this.addPixelsNeg(refLine[refPos] - 1, blackPixels); blackPixels ^= 1; if (codingLine[this.codingPos] < columns) { - if (refPos > 0) + if (refPos > 0) { --refPos; - else + } else { ++refPos; - + } while (refLine[refPos] <= codingLine[this.codingPos] && - refLine[refPos] < columns) + refLine[refPos] < columns) { refPos += 2; + } } break; case EOF: @@ -35227,8 +37269,9 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { } } - if (this.byteAlign) + if (this.byteAlign) { this.inputBits &= ~7; + } var gotEOL = false; @@ -35262,10 +37305,11 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { this.eatBits(1); } if (this.encoding >= 0) { - for (var i = 0; i < 4; ++i) { + for (i = 0; i < 4; ++i) { code1 = this.lookBits(12); - if (code1 != 1) + if (code1 != 1) { info('bad rtc code: ' + code1); + } this.eatBits(12); if (this.encoding > 0) { this.lookBits(1); @@ -35294,10 +37338,11 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { } } - if (codingLine[0] > 0) + if (codingLine[0] > 0) { this.outputBits = codingLine[this.codingPos = 0]; - else + } else { this.outputBits = codingLine[this.codingPos = 1]; + } this.row++; } @@ -35311,7 +37356,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { codingLine[this.codingPos - 1]); } } else { - var bits = 8; + bits = 8; c = 0; do { if (this.outputBits > bits) { @@ -35352,15 +37397,17 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { // returned. The second array element is the actual code. The third array // element indicates whether EOF was reached. CCITTFaxStream.prototype.findTableCode = - function ccittFaxStreamFindTableCode(start, end, table, limit) { + function ccittFaxStreamFindTableCode(start, end, table, limit) { var limitValue = limit || 0; for (var i = start; i <= end; ++i) { var code = this.lookBits(i); - if (code == EOF) + if (code == EOF) { return [true, 1, false]; - if (i < end) + } + if (i < end) { code <<= end - i; + } if (!limitValue || code >= limitValue) { var p = table[code - limitValue]; if (p[0] == i) { @@ -35373,7 +37420,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { }; CCITTFaxStream.prototype.getTwoDimCode = - function ccittFaxStreamGetTwoDimCode() { + function ccittFaxStreamGetTwoDimCode() { var code = 0; var p; @@ -35386,28 +37433,30 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { } } else { var result = this.findTableCode(1, 7, twoDimTable); - if (result[0] && result[2]) + if (result[0] && result[2]) { return result[1]; + } } info('Bad two dim code'); return EOF; }; CCITTFaxStream.prototype.getWhiteCode = - function ccittFaxStreamGetWhiteCode() { + function ccittFaxStreamGetWhiteCode() { var code = 0; var p; - var n; if (this.eoblock) { code = this.lookBits(12); - if (code == EOF) + if (code == EOF) { return 1; + } - if ((code >> 5) === 0) + if ((code >> 5) === 0) { p = whiteTable1[code]; - else + } else { p = whiteTable2[code >> 3]; + } if (p[0] > 0) { this.eatBits(p[0]); @@ -35415,12 +37464,14 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { } } else { var result = this.findTableCode(1, 9, whiteTable2); - if (result[0]) + if (result[0]) { return result[1]; + } result = this.findTableCode(11, 12, whiteTable1); - if (result[0]) + if (result[0]) { return result[1]; + } } info('bad white code'); this.eatBits(1); @@ -35428,19 +37479,21 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { }; CCITTFaxStream.prototype.getBlackCode = - function ccittFaxStreamGetBlackCode() { + function ccittFaxStreamGetBlackCode() { var code, p; if (this.eoblock) { code = this.lookBits(13); - if (code == EOF) + if (code == EOF) { return 1; - if ((code >> 7) === 0) + } + if ((code >> 7) === 0) { p = blackTable1[code]; - else if ((code >> 9) === 0 && (code >> 7) !== 0) + } else if ((code >> 9) === 0 && (code >> 7) !== 0) { p = blackTable2[(code >> 1) - 64]; - else + } else { p = blackTable3[code >> 7]; + } if (p[0] > 0) { this.eatBits(p[0]); @@ -35448,16 +37501,19 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { } } else { var result = this.findTableCode(2, 6, blackTable3); - if (result[0]) + if (result[0]) { return result[1]; + } result = this.findTableCode(7, 12, blackTable2, 64); - if (result[0]) + if (result[0]) { return result[1]; + } result = this.findTableCode(10, 13, blackTable1); - if (result[0]) + if (result[0]) { return result[1]; + } } info('bad black code'); this.eatBits(1); @@ -35468,8 +37524,9 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { var c; while (this.inputBits < n) { if ((c = this.str.getByte()) === -1) { - if (this.inputBits === 0) + if (this.inputBits === 0) { return EOF; + } return ((this.inputBuf << (n - this.inputBits)) & (0xFFFF >> (16 - n))); } @@ -35480,8 +37537,9 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { }; CCITTFaxStream.prototype.eatBits = function CCITTFaxStream_eatBits(n) { - if ((this.inputBits -= n) < 0) + if ((this.inputBits -= n) < 0) { this.inputBits = 0; + } }; return CCITTFaxStream; @@ -35540,8 +37598,9 @@ var LZWStream = (function LZWStreamClosure() { var i, j, q; var lzwState = this.lzwState; - if (!lzwState) + if (!lzwState) { return; // eof was found + } var earlyChange = lzwState.earlyChange; var nextCode = lzwState.nextCode; @@ -35602,8 +37661,9 @@ var LZWStream = (function LZWStreamClosure() { } while (estimatedDecodedSize < decodedLength); buffer = this.ensureBuffer(this.bufferLength + estimatedDecodedSize); } - for (j = 0; j < currentSequenceLength; j++) + for (j = 0; j < currentSequenceLength; j++) { buffer[currentBufferLength++] = currentSequence[j]; + } } lzwState.nextCode = nextCode; lzwState.codeLength = codeLength; @@ -35632,70 +37692,59 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { var pdfManager; function loadDocument(recoveryMode) { - var loadDocumentPromise = new LegacyPromise(); + var loadDocumentCapability = createPromiseCapability(); var parseSuccess = function parseSuccess() { - var numPagesPromise = pdfManager.ensureModel('numPages'); - var fingerprintPromise = pdfManager.ensureModel('fingerprint'); - var outlinePromise = pdfManager.ensureCatalog('documentOutline'); - var infoPromise = pdfManager.ensureModel('documentInfo'); - var metadataPromise = pdfManager.ensureCatalog('metadata'); + var numPagesPromise = pdfManager.ensureDoc('numPages'); + var fingerprintPromise = pdfManager.ensureDoc('fingerprint'); var encryptedPromise = pdfManager.ensureXRef('encrypt'); - var javaScriptPromise = pdfManager.ensureCatalog('javaScript'); - Promise.all([numPagesPromise, fingerprintPromise, outlinePromise, - infoPromise, metadataPromise, encryptedPromise, - javaScriptPromise]).then( - function onDocReady(results) { - + Promise.all([numPagesPromise, fingerprintPromise, + encryptedPromise]).then(function onDocReady(results) { var doc = { numPages: results[0], fingerprint: results[1], - outline: results[2], - info: results[3], - metadata: results[4], - encrypted: !!results[5], - javaScript: results[6] + encrypted: !!results[2], }; - loadDocumentPromise.resolve(doc); + loadDocumentCapability.resolve(doc); }, parseFailure); }; var parseFailure = function parseFailure(e) { - loadDocumentPromise.reject(e); + loadDocumentCapability.reject(e); }; - pdfManager.ensureModel('checkHeader', []).then(function() { - pdfManager.ensureModel('parseStartXRef', []).then(function() { - pdfManager.ensureModel('parse', [recoveryMode]).then( - parseSuccess, parseFailure); + pdfManager.ensureDoc('checkHeader', []).then(function() { + pdfManager.ensureDoc('parseStartXRef', []).then(function() { + pdfManager.ensureDoc('parse', [recoveryMode]).then( + parseSuccess, parseFailure); }, parseFailure); }, parseFailure); - return loadDocumentPromise; + return loadDocumentCapability.promise; } function getPdfManager(data) { - var pdfManagerPromise = new LegacyPromise(); + var pdfManagerCapability = createPromiseCapability(); var source = data.source; var disableRange = data.disableRange; if (source.data) { try { pdfManager = new LocalPdfManager(source.data, source.password); - pdfManagerPromise.resolve(); + pdfManagerCapability.resolve(); } catch (ex) { - pdfManagerPromise.reject(ex); + pdfManagerCapability.reject(ex); } - return pdfManagerPromise; + return pdfManagerCapability.promise; } else if (source.chunkedViewerLoading) { try { pdfManager = new NetworkPdfManager(source, handler); - pdfManagerPromise.resolve(); + pdfManagerCapability.resolve(); } catch (ex) { - pdfManagerPromise.reject(ex); + pdfManagerCapability.reject(ex); } - return pdfManagerPromise; + return pdfManagerCapability.promise; } var networkManager = new NetworkManager(source.url, { @@ -35740,9 +37789,9 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { try { pdfManager = new NetworkPdfManager(source, handler); - pdfManagerPromise.resolve(pdfManager); + pdfManagerCapability.resolve(pdfManager); } catch (ex) { - pdfManagerPromise.reject(ex); + pdfManagerCapability.reject(ex); } }, @@ -35750,21 +37799,21 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { // the data is array, instantiating directly from it try { pdfManager = new LocalPdfManager(args.chunk, source.password); - pdfManagerPromise.resolve(); + pdfManagerCapability.resolve(); } catch (ex) { - pdfManagerPromise.reject(ex); + pdfManagerCapability.reject(ex); } }, onError: function onError(status) { if (status == 404) { - var exception = new MissingPDFException( 'Missing PDF "' + - source.url + '".'); + var exception = new MissingPDFException('Missing PDF "' + + source.url + '".'); handler.send('MissingPDF', { exception: exception }); } else { handler.send('DocError', 'Unexpected server response (' + - status + ') while retrieving PDF "' + - source.url + '".'); + status + ') while retrieving PDF "' + + source.url + '".'); } }, @@ -35776,7 +37825,7 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { } }); - return pdfManagerPromise; + return pdfManagerCapability.promise; } handler.on('test', function wphSetupTest(data) { @@ -35846,6 +37895,7 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { PDFJS.verbosity = data.verbosity; PDFJS.cMapUrl = data.cMapUrl === undefined ? null : data.cMapUrl; + PDFJS.cMapPacked = data.cMapPacked === true; getPdfManager(data).then(function () { pdfManager.onLoadedStream().then(function(stream) { @@ -35858,9 +37908,7 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { if (ex instanceof PasswordException) { // after password exception prepare to receive a new password // to repeat loading - pdfManager.passwordChangedPromise = - new LegacyPromise(); - pdfManager.passwordChangedPromise.then(pdfManagerReady); + pdfManager.passwordChanged().then(pdfManagerReady); } onFailure(ex); @@ -35875,46 +37923,64 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { }, onFailure); }); - handler.on('GetPageRequest', function wphSetupGetPage(data) { - var pageIndex = data.pageIndex; - pdfManager.getPage(pageIndex).then(function(page) { + handler.on('GetPage', function wphSetupGetPage(data) { + return pdfManager.getPage(data.pageIndex).then(function(page) { var rotatePromise = pdfManager.ensure(page, 'rotate'); var refPromise = pdfManager.ensure(page, 'ref'); var viewPromise = pdfManager.ensure(page, 'view'); - Promise.all([rotatePromise, refPromise, viewPromise]).then( + return Promise.all([rotatePromise, refPromise, viewPromise]).then( function(results) { - var page = { - pageIndex: data.pageIndex, + return { rotate: results[0], ref: results[1], view: results[2] }; - - handler.send('GetPage', { pageInfo: page }); }); }); }); - handler.on('GetPageIndex', function wphSetupGetPageIndex(data, deferred) { + handler.on('GetPageIndex', function wphSetupGetPageIndex(data) { var ref = new Ref(data.ref.num, data.ref.gen); - pdfManager.pdfModel.catalog.getPageIndex(ref).then(function (pageIndex) { - deferred.resolve(pageIndex); - }, deferred.reject); + var catalog = pdfManager.pdfDocument.catalog; + return catalog.getPageIndex(ref); }); handler.on('GetDestinations', - function wphSetupGetDestinations(data, deferred) { - pdfManager.ensureCatalog('destinations').then(function(destinations) { - deferred.resolve(destinations); - }); + function wphSetupGetDestinations(data) { + return pdfManager.ensureCatalog('destinations'); + } + ); + + handler.on('GetAttachments', + function wphSetupGetAttachments(data) { + return pdfManager.ensureCatalog('attachments'); + } + ); + + handler.on('GetJavaScript', + function wphSetupGetJavaScript(data) { + return pdfManager.ensureCatalog('javaScript'); + } + ); + + handler.on('GetOutline', + function wphSetupGetOutline(data) { + return pdfManager.ensureCatalog('documentOutline'); } ); - handler.on('GetData', function wphSetupGetData(data, deferred) { + handler.on('GetMetadata', + function wphSetupGetMetadata(data) { + return Promise.all([pdfManager.ensureDoc('documentInfo'), + pdfManager.ensureCatalog('metadata')]); + } + ); + + handler.on('GetData', function wphSetupGetData(data) { pdfManager.requestLoadedStream(); - pdfManager.onLoadedStream().then(function(stream) { - deferred.resolve(stream.bytes); + return pdfManager.onLoadedStream().then(function(stream) { + return stream.bytes; }); }); @@ -35922,16 +37988,9 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { pdfManager.updatePassword(data); }); - handler.on('GetAnnotationsRequest', function wphSetupGetAnnotations(data) { - pdfManager.getPage(data.pageIndex).then(function(page) { - pdfManager.ensure(page, 'getAnnotationsData', []).then( - function(annotationsData) { - handler.send('GetAnnotations', { - pageIndex: data.pageIndex, - annotations: annotationsData - }); - } - ); + handler.on('GetAnnotations', function wphSetupGetAnnotations(data) { + return pdfManager.getPage(data.pageIndex).then(function(page) { + return pdfManager.ensure(page, 'getAnnotationsData', []); }); }); @@ -35949,7 +38008,7 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { }, function(e) { var minimumStackMessage = - 'worker.js: while trying to getPage() and getOperatorList()'; + 'worker.js: while trying to getPage() and getOperatorList()'; var wrappedException; @@ -35980,29 +38039,24 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { }); }, this); - handler.on('GetTextContent', function wphExtractText(data, deferred) { - pdfManager.getPage(data.pageIndex).then(function(page) { + handler.on('GetTextContent', function wphExtractText(data) { + return pdfManager.getPage(data.pageIndex).then(function(page) { var pageNum = data.pageIndex + 1; var start = Date.now(); - page.extractTextContent().then(function(textContent) { - deferred.resolve(textContent); + return page.extractTextContent().then(function(textContent) { info('text indexing: page=' + pageNum + ' - time=' + (Date.now() - start) + 'ms'); - }, function (e) { - // Skip errored pages - deferred.reject(e); + return textContent; }); }); }); - handler.on('Cleanup', function wphCleanup(data, deferred) { - pdfManager.cleanup(); - deferred.resolve(true); + handler.on('Cleanup', function wphCleanup(data) { + return pdfManager.cleanup(); }); - handler.on('Terminate', function wphTerminate(data, deferred) { + handler.on('Terminate', function wphTerminate(data) { pdfManager.terminate(); - deferred.resolve(); }); } }; @@ -36228,6 +38282,990 @@ var ArithmeticDecoder = (function ArithmeticDecoderClosure() { })(); +var JpegImage = (function jpegImage() { + var dctZigZag = new Int32Array([ + 0, + 1, 8, + 16, 9, 2, + 3, 10, 17, 24, + 32, 25, 18, 11, 4, + 5, 12, 19, 26, 33, 40, + 48, 41, 34, 27, 20, 13, 6, + 7, 14, 21, 28, 35, 42, 49, 56, + 57, 50, 43, 36, 29, 22, 15, + 23, 30, 37, 44, 51, 58, + 59, 52, 45, 38, 31, + 39, 46, 53, 60, + 61, 54, 47, + 55, 62, + 63 + ]); + + var dctCos1 = 4017; // cos(pi/16) + var dctSin1 = 799; // sin(pi/16) + var dctCos3 = 3406; // cos(3*pi/16) + var dctSin3 = 2276; // sin(3*pi/16) + var dctCos6 = 1567; // cos(6*pi/16) + var dctSin6 = 3784; // sin(6*pi/16) + var dctSqrt2 = 5793; // sqrt(2) + var dctSqrt1d2 = 2896; // sqrt(2) / 2 + + function constructor() { + } + + function buildHuffmanTable(codeLengths, values) { + var k = 0, code = [], i, j, length = 16; + while (length > 0 && !codeLengths[length - 1]) { + length--; + } + code.push({children: [], index: 0}); + var p = code[0], q; + for (i = 0; i < length; i++) { + for (j = 0; j < codeLengths[i]; j++) { + p = code.pop(); + p.children[p.index] = values[k]; + while (p.index > 0) { + p = code.pop(); + } + p.index++; + code.push(p); + while (code.length <= i) { + code.push(q = {children: [], index: 0}); + p.children[p.index] = q.children; + p = q; + } + k++; + } + if (i + 1 < length) { + // p here points to last code + code.push(q = {children: [], index: 0}); + p.children[p.index] = q.children; + p = q; + } + } + return code[0].children; + } + + function getBlockBufferOffset(component, row, col) { + return 64 * ((component.blocksPerLine + 1) * row + col); + } + + function decodeScan(data, offset, + frame, components, resetInterval, + spectralStart, spectralEnd, + successivePrev, successive) { + var precision = frame.precision; + var samplesPerLine = frame.samplesPerLine; + var scanLines = frame.scanLines; + var mcusPerLine = frame.mcusPerLine; + var progressive = frame.progressive; + var maxH = frame.maxH, maxV = frame.maxV; + + var startOffset = offset, bitsData = 0, bitsCount = 0; + + function readBit() { + if (bitsCount > 0) { + bitsCount--; + return (bitsData >> bitsCount) & 1; + } + bitsData = data[offset++]; + if (bitsData == 0xFF) { + var nextByte = data[offset++]; + if (nextByte) { + throw 'unexpected marker: ' + + ((bitsData << 8) | nextByte).toString(16); + } + // unstuff 0 + } + bitsCount = 7; + return bitsData >>> 7; + } + + function decodeHuffman(tree) { + var node = tree; + var bit; + while ((bit = readBit()) !== null) { + node = node[bit]; + if (typeof node === 'number') { + return node; + } + if (typeof node !== 'object') { + throw 'invalid huffman sequence'; + } + } + return null; + } + + function receive(length) { + var n = 0; + while (length > 0) { + var bit = readBit(); + if (bit === null) { + return; + } + n = (n << 1) | bit; + length--; + } + return n; + } + + function receiveAndExtend(length) { + var n = receive(length); + if (n >= 1 << (length - 1)) { + return n; + } + return n + (-1 << length) + 1; + } + + function decodeBaseline(component, offset) { + var t = decodeHuffman(component.huffmanTableDC); + var diff = t === 0 ? 0 : receiveAndExtend(t); + component.blockData[offset] = (component.pred += diff); + var k = 1; + while (k < 64) { + var rs = decodeHuffman(component.huffmanTableAC); + var s = rs & 15, r = rs >> 4; + if (s === 0) { + if (r < 15) { + break; + } + k += 16; + continue; + } + k += r; + var z = dctZigZag[k]; + component.blockData[offset + z] = receiveAndExtend(s); + k++; + } + } + + function decodeDCFirst(component, offset) { + var t = decodeHuffman(component.huffmanTableDC); + var diff = t === 0 ? 0 : (receiveAndExtend(t) << successive); + component.blockData[offset] = (component.pred += diff); + } + + function decodeDCSuccessive(component, offset) { + component.blockData[offset] |= readBit() << successive; + } + + var eobrun = 0; + function decodeACFirst(component, offset) { + if (eobrun > 0) { + eobrun--; + return; + } + var k = spectralStart, e = spectralEnd; + while (k <= e) { + var rs = decodeHuffman(component.huffmanTableAC); + var s = rs & 15, r = rs >> 4; + if (s === 0) { + if (r < 15) { + eobrun = receive(r) + (1 << r) - 1; + break; + } + k += 16; + continue; + } + k += r; + var z = dctZigZag[k]; + component.blockData[offset + z] = + receiveAndExtend(s) * (1 << successive); + k++; + } + } + + var successiveACState = 0, successiveACNextValue; + function decodeACSuccessive(component, offset) { + var k = spectralStart; + var e = spectralEnd; + var r = 0; + var s; + var rs; + while (k <= e) { + var z = dctZigZag[k]; + switch (successiveACState) { + case 0: // initial state + rs = decodeHuffman(component.huffmanTableAC); + s = rs & 15; + r = rs >> 4; + if (s === 0) { + if (r < 15) { + eobrun = receive(r) + (1 << r); + successiveACState = 4; + } else { + r = 16; + successiveACState = 1; + } + } else { + if (s !== 1) { + throw 'invalid ACn encoding'; + } + successiveACNextValue = receiveAndExtend(s); + successiveACState = r ? 2 : 3; + } + continue; + case 1: // skipping r zero items + case 2: + if (component.blockData[offset + z]) { + component.blockData[offset + z] += (readBit() << successive); + } else { + r--; + if (r === 0) { + successiveACState = successiveACState == 2 ? 3 : 0; + } + } + break; + case 3: // set value for a zero item + if (component.blockData[offset + z]) { + component.blockData[offset + z] += (readBit() << successive); + } else { + component.blockData[offset + z] = + successiveACNextValue << successive; + successiveACState = 0; + } + break; + case 4: // eob + if (component.blockData[offset + z]) { + component.blockData[offset + z] += (readBit() << successive); + } + break; + } + k++; + } + if (successiveACState === 4) { + eobrun--; + if (eobrun === 0) { + successiveACState = 0; + } + } + } + + function decodeMcu(component, decode, mcu, row, col) { + var mcuRow = (mcu / mcusPerLine) | 0; + var mcuCol = mcu % mcusPerLine; + var blockRow = mcuRow * component.v + row; + var blockCol = mcuCol * component.h + col; + var offset = getBlockBufferOffset(component, blockRow, blockCol); + decode(component, offset); + } + + function decodeBlock(component, decode, mcu) { + var blockRow = (mcu / component.blocksPerLine) | 0; + var blockCol = mcu % component.blocksPerLine; + var offset = getBlockBufferOffset(component, blockRow, blockCol); + decode(component, offset); + } + + var componentsLength = components.length; + var component, i, j, k, n; + var decodeFn; + if (progressive) { + if (spectralStart === 0) { + decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive; + } else { + decodeFn = successivePrev === 0 ? decodeACFirst : decodeACSuccessive; + } + } else { + decodeFn = decodeBaseline; + } + + var mcu = 0, marker; + var mcuExpected; + if (componentsLength == 1) { + mcuExpected = components[0].blocksPerLine * components[0].blocksPerColumn; + } else { + mcuExpected = mcusPerLine * frame.mcusPerColumn; + } + if (!resetInterval) { + resetInterval = mcuExpected; + } + + var h, v; + while (mcu < mcuExpected) { + // reset interval stuff + for (i = 0; i < componentsLength; i++) { + components[i].pred = 0; + } + eobrun = 0; + + if (componentsLength == 1) { + component = components[0]; + for (n = 0; n < resetInterval; n++) { + decodeBlock(component, decodeFn, mcu); + mcu++; + } + } else { + for (n = 0; n < resetInterval; n++) { + for (i = 0; i < componentsLength; i++) { + component = components[i]; + h = component.h; + v = component.v; + for (j = 0; j < v; j++) { + for (k = 0; k < h; k++) { + decodeMcu(component, decodeFn, mcu, j, k); + } + } + } + mcu++; + } + } + + // find marker + bitsCount = 0; + marker = (data[offset] << 8) | data[offset + 1]; + if (marker <= 0xFF00) { + throw 'marker was not found'; + } + + if (marker >= 0xFFD0 && marker <= 0xFFD7) { // RSTx + offset += 2; + } else { + break; + } + } + + return offset - startOffset; + } + + // A port of poppler's IDCT method which in turn is taken from: + // Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz, + // 'Practical Fast 1-D DCT Algorithms with 11 Multiplications', + // IEEE Intl. Conf. on Acoustics, Speech & Signal Processing, 1989, + // 988-991. + function quantizeAndInverse(component, blockBufferOffset, p) { + var qt = component.quantizationTable; + var v0, v1, v2, v3, v4, v5, v6, v7, t; + var i; + + // dequant + for (i = 0; i < 64; i++) { + p[i] = component.blockData[blockBufferOffset + i] * qt[i]; + } + + // inverse DCT on rows + for (i = 0; i < 8; ++i) { + var row = 8 * i; + + // check for all-zero AC coefficients + if (p[1 + row] === 0 && p[2 + row] === 0 && p[3 + row] === 0 && + p[4 + row] === 0 && p[5 + row] === 0 && p[6 + row] === 0 && + p[7 + row] === 0) { + t = (dctSqrt2 * p[0 + row] + 512) >> 10; + p[0 + row] = t; + p[1 + row] = t; + p[2 + row] = t; + p[3 + row] = t; + p[4 + row] = t; + p[5 + row] = t; + p[6 + row] = t; + p[7 + row] = t; + continue; + } + + // stage 4 + v0 = (dctSqrt2 * p[0 + row] + 128) >> 8; + v1 = (dctSqrt2 * p[4 + row] + 128) >> 8; + v2 = p[2 + row]; + v3 = p[6 + row]; + v4 = (dctSqrt1d2 * (p[1 + row] - p[7 + row]) + 128) >> 8; + v7 = (dctSqrt1d2 * (p[1 + row] + p[7 + row]) + 128) >> 8; + v5 = p[3 + row] << 4; + v6 = p[5 + row] << 4; + + // stage 3 + t = (v0 - v1+ 1) >> 1; + v0 = (v0 + v1 + 1) >> 1; + v1 = t; + t = (v2 * dctSin6 + v3 * dctCos6 + 128) >> 8; + v2 = (v2 * dctCos6 - v3 * dctSin6 + 128) >> 8; + v3 = t; + t = (v4 - v6 + 1) >> 1; + v4 = (v4 + v6 + 1) >> 1; + v6 = t; + t = (v7 + v5 + 1) >> 1; + v5 = (v7 - v5 + 1) >> 1; + v7 = t; + + // stage 2 + t = (v0 - v3 + 1) >> 1; + v0 = (v0 + v3 + 1) >> 1; + v3 = t; + t = (v1 - v2 + 1) >> 1; + v1 = (v1 + v2 + 1) >> 1; + v2 = t; + t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12; + v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12; + v7 = t; + t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12; + v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12; + v6 = t; + + // stage 1 + p[0 + row] = v0 + v7; + p[7 + row] = v0 - v7; + p[1 + row] = v1 + v6; + p[6 + row] = v1 - v6; + p[2 + row] = v2 + v5; + p[5 + row] = v2 - v5; + p[3 + row] = v3 + v4; + p[4 + row] = v3 - v4; + } + + // inverse DCT on columns + for (i = 0; i < 8; ++i) { + var col = i; + + // check for all-zero AC coefficients + if (p[1*8 + col] === 0 && p[2*8 + col] === 0 && p[3*8 + col] === 0 && + p[4*8 + col] === 0 && p[5*8 + col] === 0 && p[6*8 + col] === 0 && + p[7*8 + col] === 0) { + t = (dctSqrt2 * p[i+0] + 8192) >> 14; + p[0*8 + col] = t; + p[1*8 + col] = t; + p[2*8 + col] = t; + p[3*8 + col] = t; + p[4*8 + col] = t; + p[5*8 + col] = t; + p[6*8 + col] = t; + p[7*8 + col] = t; + continue; + } + + // stage 4 + v0 = (dctSqrt2 * p[0*8 + col] + 2048) >> 12; + v1 = (dctSqrt2 * p[4*8 + col] + 2048) >> 12; + v2 = p[2*8 + col]; + v3 = p[6*8 + col]; + v4 = (dctSqrt1d2 * (p[1*8 + col] - p[7*8 + col]) + 2048) >> 12; + v7 = (dctSqrt1d2 * (p[1*8 + col] + p[7*8 + col]) + 2048) >> 12; + v5 = p[3*8 + col]; + v6 = p[5*8 + col]; + + // stage 3 + t = (v0 - v1 + 1) >> 1; + v0 = (v0 + v1 + 1) >> 1; + v1 = t; + t = (v2 * dctSin6 + v3 * dctCos6 + 2048) >> 12; + v2 = (v2 * dctCos6 - v3 * dctSin6 + 2048) >> 12; + v3 = t; + t = (v4 - v6 + 1) >> 1; + v4 = (v4 + v6 + 1) >> 1; + v6 = t; + t = (v7 + v5 + 1) >> 1; + v5 = (v7 - v5 + 1) >> 1; + v7 = t; + + // stage 2 + t = (v0 - v3 + 1) >> 1; + v0 = (v0 + v3 + 1) >> 1; + v3 = t; + t = (v1 - v2 + 1) >> 1; + v1 = (v1 + v2 + 1) >> 1; + v2 = t; + t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12; + v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12; + v7 = t; + t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12; + v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12; + v6 = t; + + // stage 1 + p[0*8 + col] = v0 + v7; + p[7*8 + col] = v0 - v7; + p[1*8 + col] = v1 + v6; + p[6*8 + col] = v1 - v6; + p[2*8 + col] = v2 + v5; + p[5*8 + col] = v2 - v5; + p[3*8 + col] = v3 + v4; + p[4*8 + col] = v3 - v4; + } + + // convert to 8-bit integers + for (i = 0; i < 64; ++i) { + var index = blockBufferOffset + i; + var q = p[i]; + q = (q <= -2056) ? 0 : (q >= 2024) ? 255 : (q + 2056) >> 4; + component.blockData[index] = q; + } + } + + function buildComponentData(frame, component) { + var lines = []; + var blocksPerLine = component.blocksPerLine; + var blocksPerColumn = component.blocksPerColumn; + var samplesPerLine = blocksPerLine << 3; + var computationBuffer = new Int32Array(64); + + var i, j, ll = 0; + for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) { + for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) { + var offset = getBlockBufferOffset(component, blockRow, blockCol); + quantizeAndInverse(component, offset, computationBuffer); + } + } + return component.blockData; + } + + function clamp0to255(a) { + return a <= 0 ? 0 : a >= 255 ? 255 : a; + } + + constructor.prototype = { + parse: function parse(data) { + + function readUint16() { + var value = (data[offset] << 8) | data[offset + 1]; + offset += 2; + return value; + } + + function readDataBlock() { + var length = readUint16(); + var array = data.subarray(offset, offset + length - 2); + offset += array.length; + return array; + } + + function prepareComponents(frame) { + var mcusPerLine = Math.ceil(frame.samplesPerLine / 8 / frame.maxH); + var mcusPerColumn = Math.ceil(frame.scanLines / 8 / frame.maxV); + for (var i = 0; i < frame.components.length; i++) { + component = frame.components[i]; + var blocksPerLine = Math.ceil(Math.ceil(frame.samplesPerLine / 8) * + component.h / frame.maxH); + var blocksPerColumn = Math.ceil(Math.ceil(frame.scanLines / 8) * + component.v / frame.maxV); + var blocksPerLineForMcu = mcusPerLine * component.h; + var blocksPerColumnForMcu = mcusPerColumn * component.v; + + var blocksBufferSize = 64 * blocksPerColumnForMcu * + (blocksPerLineForMcu + 1); + component.blockData = new Int16Array(blocksBufferSize); + component.blocksPerLine = blocksPerLine; + component.blocksPerColumn = blocksPerColumn; + } + frame.mcusPerLine = mcusPerLine; + frame.mcusPerColumn = mcusPerColumn; + } + + var offset = 0, length = data.length; + var jfif = null; + var adobe = null; + var pixels = null; + var frame, resetInterval; + var quantizationTables = []; + var huffmanTablesAC = [], huffmanTablesDC = []; + var fileMarker = readUint16(); + if (fileMarker != 0xFFD8) { // SOI (Start of Image) + throw 'SOI not found'; + } + + fileMarker = readUint16(); + while (fileMarker != 0xFFD9) { // EOI (End of image) + var i, j, l; + switch(fileMarker) { + case 0xFFE0: // APP0 (Application Specific) + case 0xFFE1: // APP1 + case 0xFFE2: // APP2 + case 0xFFE3: // APP3 + case 0xFFE4: // APP4 + case 0xFFE5: // APP5 + case 0xFFE6: // APP6 + case 0xFFE7: // APP7 + case 0xFFE8: // APP8 + case 0xFFE9: // APP9 + case 0xFFEA: // APP10 + case 0xFFEB: // APP11 + case 0xFFEC: // APP12 + case 0xFFED: // APP13 + case 0xFFEE: // APP14 + case 0xFFEF: // APP15 + case 0xFFFE: // COM (Comment) + var appData = readDataBlock(); + + if (fileMarker === 0xFFE0) { + if (appData[0] === 0x4A && appData[1] === 0x46 && + appData[2] === 0x49 && appData[3] === 0x46 && + appData[4] === 0) { // 'JFIF\x00' + jfif = { + version: { major: appData[5], minor: appData[6] }, + densityUnits: appData[7], + xDensity: (appData[8] << 8) | appData[9], + yDensity: (appData[10] << 8) | appData[11], + thumbWidth: appData[12], + thumbHeight: appData[13], + thumbData: appData.subarray(14, 14 + + 3 * appData[12] * appData[13]) + }; + } + } + // TODO APP1 - Exif + if (fileMarker === 0xFFEE) { + if (appData[0] === 0x41 && appData[1] === 0x64 && + appData[2] === 0x6F && appData[3] === 0x62 && + appData[4] === 0x65 && appData[5] === 0) { // 'Adobe\x00' + adobe = { + version: appData[6], + flags0: (appData[7] << 8) | appData[8], + flags1: (appData[9] << 8) | appData[10], + transformCode: appData[11] + }; + } + } + break; + + case 0xFFDB: // DQT (Define Quantization Tables) + var quantizationTablesLength = readUint16(); + var quantizationTablesEnd = quantizationTablesLength + offset - 2; + var z; + while (offset < quantizationTablesEnd) { + var quantizationTableSpec = data[offset++]; + var tableData = new Int32Array(64); + if ((quantizationTableSpec >> 4) === 0) { // 8 bit values + for (j = 0; j < 64; j++) { + z = dctZigZag[j]; + tableData[z] = data[offset++]; + } + } else if ((quantizationTableSpec >> 4) === 1) { //16 bit + for (j = 0; j < 64; j++) { + z = dctZigZag[j]; + tableData[z] = readUint16(); + } + } else { + throw 'DQT: invalid table spec'; + } + quantizationTables[quantizationTableSpec & 15] = tableData; + } + break; + + case 0xFFC0: // SOF0 (Start of Frame, Baseline DCT) + case 0xFFC1: // SOF1 (Start of Frame, Extended DCT) + case 0xFFC2: // SOF2 (Start of Frame, Progressive DCT) + if (frame) { + throw 'Only single frame JPEGs supported'; + } + readUint16(); // skip data length + frame = {}; + frame.extended = (fileMarker === 0xFFC1); + frame.progressive = (fileMarker === 0xFFC2); + frame.precision = data[offset++]; + frame.scanLines = readUint16(); + frame.samplesPerLine = readUint16(); + frame.components = []; + frame.componentIds = {}; + var componentsCount = data[offset++], componentId; + var maxH = 0, maxV = 0; + for (i = 0; i < componentsCount; i++) { + componentId = data[offset]; + var h = data[offset + 1] >> 4; + var v = data[offset + 1] & 15; + if (maxH < h) { + maxH = h; + } + if (maxV < v) { + maxV = v; + } + var qId = data[offset + 2]; + l = frame.components.push({ + h: h, + v: v, + quantizationTable: quantizationTables[qId] + }); + frame.componentIds[componentId] = l - 1; + offset += 3; + } + frame.maxH = maxH; + frame.maxV = maxV; + prepareComponents(frame); + break; + + case 0xFFC4: // DHT (Define Huffman Tables) + var huffmanLength = readUint16(); + for (i = 2; i < huffmanLength;) { + var huffmanTableSpec = data[offset++]; + var codeLengths = new Uint8Array(16); + var codeLengthSum = 0; + for (j = 0; j < 16; j++, offset++) { + codeLengthSum += (codeLengths[j] = data[offset]); + } + var huffmanValues = new Uint8Array(codeLengthSum); + for (j = 0; j < codeLengthSum; j++, offset++) { + huffmanValues[j] = data[offset]; + } + i += 17 + codeLengthSum; + + ((huffmanTableSpec >> 4) === 0 ? + huffmanTablesDC : huffmanTablesAC)[huffmanTableSpec & 15] = + buildHuffmanTable(codeLengths, huffmanValues); + } + break; + + case 0xFFDD: // DRI (Define Restart Interval) + readUint16(); // skip data length + resetInterval = readUint16(); + break; + + case 0xFFDA: // SOS (Start of Scan) + var scanLength = readUint16(); + var selectorsCount = data[offset++]; + var components = [], component; + for (i = 0; i < selectorsCount; i++) { + var componentIndex = frame.componentIds[data[offset++]]; + component = frame.components[componentIndex]; + var tableSpec = data[offset++]; + component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4]; + component.huffmanTableAC = huffmanTablesAC[tableSpec & 15]; + components.push(component); + } + var spectralStart = data[offset++]; + var spectralEnd = data[offset++]; + var successiveApproximation = data[offset++]; + var processed = decodeScan(data, offset, + frame, components, resetInterval, + spectralStart, spectralEnd, + successiveApproximation >> 4, successiveApproximation & 15); + offset += processed; + break; + default: + if (data[offset - 3] == 0xFF && + data[offset - 2] >= 0xC0 && data[offset - 2] <= 0xFE) { + // could be incorrect encoding -- last 0xFF byte of the previous + // block was eaten by the encoder + offset -= 3; + break; + } + throw 'unknown JPEG marker ' + fileMarker.toString(16); + } + fileMarker = readUint16(); + } + + this.width = frame.samplesPerLine; + this.height = frame.scanLines; + this.jfif = jfif; + this.adobe = adobe; + this.components = []; + for (i = 0; i < frame.components.length; i++) { + component = frame.components[i]; + this.components.push({ + output: buildComponentData(frame, component), + scaleX: component.h / frame.maxH, + scaleY: component.v / frame.maxV, + blocksPerLine: component.blocksPerLine, + blocksPerColumn: component.blocksPerColumn + }); + } + this.numComponents = this.components.length; + }, + + _getLinearizedBlockData: function getLinearizedBlockData(width, height) { + var scaleX = this.width / width, scaleY = this.height / height; + + var component, componentScaleX, componentScaleY, blocksPerScanline; + var x, y, i, j; + var index; + var offset = 0; + var output; + var numComponents = this.components.length; + var dataLength = width * height * numComponents; + var data = new Uint8Array(dataLength); + var xScaleBlockOffset = new Uint32Array(width); + var mask3LSB = 0xfffffff8; // used to clear the 3 LSBs + + for (i = 0; i < numComponents; i++) { + component = this.components[i]; + componentScaleX = component.scaleX * scaleX; + componentScaleY = component.scaleY * scaleY; + offset = i; + output = component.output; + blocksPerScanline = (component.blocksPerLine + 1) << 3; + // precalculate the xScaleBlockOffset + for (x = 0; x < width; x++) { + j = 0 | (x * componentScaleX); + xScaleBlockOffset[x] = ((j & mask3LSB) << 3) | (j & 7); + } + // linearize the blocks of the component + for (y = 0; y < height; y++) { + j = 0 | (y * componentScaleY); + index = blocksPerScanline * (j & mask3LSB) | ((j & 7) << 3); + for (x = 0; x < width; x++) { + data[offset] = output[index + xScaleBlockOffset[x]]; + offset += numComponents; + } + } + } + return data; + }, + + _isColorConversionNeeded: function isColorConversionNeeded() { + if (this.adobe && this.adobe.transformCode) { + // The adobe transform marker overrides any previous setting + return true; + } else if (this.numComponents == 3) { + return true; + } else if (typeof this.colorTransform !== 'undefined') { + return !!this.colorTransform; + } else { + return false; + } + }, + + _convertYccToRgb: function convertYccToRgb(data) { + var Y, Cb, Cr; + for (var i = 0; i < data.length; i += this.numComponents) { + Y = data[i ]; + Cb = data[i + 1]; + Cr = data[i + 2]; + data[i ] = clamp0to255(Y - 179.456 + 1.402 * Cr); + data[i + 1] = clamp0to255(Y + 135.459 - 0.344 * Cb - 0.714 * Cr); + data[i + 2] = clamp0to255(Y - 226.816 + 1.772 * Cb); + } + return data; + }, + + _convertYcckToRgb: function convertYcckToRgb(data) { + var Y, Cb, Cr, k, CbCb, CbCr, CbY, Cbk, CrCr, Crk, CrY, YY, Yk, kk; + var offset = 0; + for (var i = 0; i < data.length; i += this.numComponents) { + Y = data[i]; + Cb = data[i + 1]; + Cr = data[i + 2]; + k = data[i + 3]; + + CbCb = Cb * Cb; + CbCr = Cb * Cr; + CbY = Cb * Y; + Cbk = Cb * k; + CrCr = Cr * Cr; + Crk = Cr * k; + CrY = Cr * Y; + YY = Y * Y; + Yk = Y * k; + kk = k * k; + + var r = - 122.67195406894 - + 6.60635669420364e-5 * CbCb + 0.000437130475926232 * CbCr - + 5.4080610064599e-5* CbY + 0.00048449797120281* Cbk - + 0.154362151871126 * Cb - 0.000957964378445773 * CrCr + + 0.000817076911346625 * CrY - 0.00477271405408747 * Crk + + 1.53380253221734 * Cr + 0.000961250184130688 * YY - + 0.00266257332283933 * Yk + 0.48357088451265 * Y - + 0.000336197177618394 * kk + 0.484791561490776 * k; + + var g = 107.268039397724 + + 2.19927104525741e-5 * CbCb - 0.000640992018297945 * CbCr + + 0.000659397001245577* CbY + 0.000426105652938837* Cbk - + 0.176491792462875 * Cb - 0.000778269941513683 * CrCr + + 0.00130872261408275 * CrY + 0.000770482631801132 * Crk - + 0.151051492775562 * Cr + 0.00126935368114843 * YY - + 0.00265090189010898 * Yk + 0.25802910206845 * Y - + 0.000318913117588328 * kk - 0.213742400323665 * k; + + var b = - 20.810012546947 - + 0.000570115196973677 * CbCb - 2.63409051004589e-5 * CbCr + + 0.0020741088115012* CbY - 0.00288260236853442* Cbk + + 0.814272968359295 * Cb - 1.53496057440975e-5 * CrCr - + 0.000132689043961446 * CrY + 0.000560833691242812 * Crk - + 0.195152027534049 * Cr + 0.00174418132927582 * YY - + 0.00255243321439347 * Yk + 0.116935020465145 * Y - + 0.000343531996510555 * kk + 0.24165260232407 * k; + + data[offset++] = clamp0to255(r); + data[offset++] = clamp0to255(g); + data[offset++] = clamp0to255(b); + } + return data; + }, + + _convertYcckToCmyk: function convertYcckToCmyk(data) { + var Y, Cb, Cr; + for (var i = 0; i < data.length; i += this.numComponents) { + Y = data[i]; + Cb = data[i + 1]; + Cr = data[i + 2]; + data[i ] = clamp0to255(434.456 - Y - 1.402 * Cr); + data[i + 1] = clamp0to255(119.541 - Y + 0.344 * Cb + 0.714 * Cr); + data[i + 2] = clamp0to255(481.816 - Y - 1.772 * Cb); + // K in data[i + 3] is unchanged + } + return data; + }, + + _convertCmykToRgb: function convertCmykToRgb(data) { + var c, m, y, k; + var offset = 0; + var length = data.length; + var min = -255 * 255 * 255; + var scale = 1 / 255 / 255; + for (var i = 0; i < length;) { + c = data[i++]; + m = data[i++]; + y = data[i++]; + k = data[i++]; + + var r = + c * (-4.387332384609988 * c + 54.48615194189176 * m + + 18.82290502165302 * y + 212.25662451639585 * k - + 72734.4411664936) + + m * (1.7149763477362134 * m - 5.6096736904047315 * y - + 17.873870861415444 * k - 1401.7366389350734) + + y * (-2.5217340131683033 * y - 21.248923337353073 * k + + 4465.541406466231) - + k * (21.86122147463605 * k + 48317.86113160301); + var g = + c * (8.841041422036149 * c + 60.118027045597366 * m + + 6.871425592049007 * y + 31.159100130055922 * k - + 20220.756542821975) + + m * (-15.310361306967817 * m + 17.575251261109482 * y + + 131.35250912493976 * k - 48691.05921601825) + + y * (4.444339102852739 * y + 9.8632861493405 * k - + 6341.191035517494) - + k * (20.737325471181034 * k + 47890.15695978492); + var b = + c * (0.8842522430003296 * c + 8.078677503112928 * m + + 30.89978309703729 * y - 0.23883238689178934 * k - + 3616.812083916688) + + m * (10.49593273432072 * m + 63.02378494754052 * y + + 50.606957656360734 * k - 28620.90484698408) + + y * (0.03296041114873217 * y + 115.60384449646641 * k - + 49363.43385999684) - + k * (22.33816807309886 * k + 45932.16563550634); + + data[offset++] = r >= 0 ? 255 : r <= min ? 0 : 255 + r * scale | 0; + data[offset++] = g >= 0 ? 255 : g <= min ? 0 : 255 + g * scale | 0; + data[offset++] = b >= 0 ? 255 : b <= min ? 0 : 255 + b * scale | 0; + } + return data; + }, + + getData: function getData(width, height, forceRGBoutput) { + if (this.numComponents > 4) { + throw 'Unsupported color mode'; + } + // type of data: Uint8Array(width * height * numComponents) + var data = this._getLinearizedBlockData(width, height); + + if (this.numComponents === 3) { + return this._convertYccToRgb(data); + } else if (this.numComponents === 4) { + if (this._isColorConversionNeeded()) { + if (forceRGBoutput) { + return this._convertYcckToRgb(data); + } else { + return this._convertYcckToCmyk(data); + } + } else { + return this._convertCmykToRgb(data); + } + } + return data; + } + }; + + return constructor; +})(); + + var JpxImage = (function JpxImageClosure() { // Table E.1 var SubbandsGainLog2 = { @@ -36236,10 +39274,6 @@ var JpxImage = (function JpxImageClosure() { 'HL': 1, 'HH': 2 }; - var TransformType = { - IRREVERSIBLE: 0, - REVERSIBLE: 1 - }; function JpxImage() { this.failOnCorruptedImage = false; } @@ -36259,15 +39293,8 @@ var JpxImage = (function JpxImageClosure() { xhr.send(null); }, parse: function JpxImage_parse(data) { - function readUint(data, offset, bytes) { - var n = 0; - for (var i = 0; i < bytes; i++) { - n = n * 256 + (data[offset + i] & 0xFF); - } - return n; - } - var head = readUint(data, 0, 2); + var head = readUint16(data, 0); // No box header, immediate start of codestream (SOC) if (head === 0xFF4F) { this.parseCodestream(data, 0, data.length); @@ -36277,11 +39304,14 @@ var JpxImage = (function JpxImageClosure() { var position = 0, length = data.length; while (position < length) { var headerSize = 8; - var lbox = readUint(data, position, 4); - var tbox = readUint(data, position + 4, 4); + var lbox = readUint32(data, position); + var tbox = readUint32(data, position + 4); position += headerSize; - if (lbox == 1) { - lbox = readUint(data, position, 8); + if (lbox === 1) { + // XLBox: read UInt64 according to spec. + // JavaScript's int precision of 53 bit should be sufficient here. + lbox = readUint32(data, position) * 4294967296 + + readUint32(data, position + 4); position += 8; headerSize += 8; } @@ -36312,15 +39342,48 @@ var JpxImage = (function JpxImageClosure() { } } }, + parseImageProperties: function JpxImage_parseImageProperties(stream) { + try { + var newByte = stream.getByte(); + while (newByte >= 0) { + var oldByte = newByte; + newByte = stream.getByte(); + var code = (oldByte << 8) | newByte; + // Image and tile size (SIZ) + if (code == 0xFF51) { + stream.skip(4); + var Xsiz = stream.getInt32() >>> 0; // Byte 4 + var Ysiz = stream.getInt32() >>> 0; // Byte 8 + var XOsiz = stream.getInt32() >>> 0; // Byte 12 + var YOsiz = stream.getInt32() >>> 0; // Byte 16 + stream.skip(16); + var Csiz = stream.getUint16(); // Byte 36 + this.width = Xsiz - XOsiz; + this.height = Ysiz - YOsiz; + this.componentsCount = Csiz; + // Results are always returned as Uint8Arrays + this.bitsPerComponent = 8; + return; + } + } + throw 'No size marker found in JPX stream'; + } catch (e) { + if (this.failOnCorruptedImage) { + error('JPX error: ' + e); + } else { + warn('JPX error: ' + e + '. Trying to recover'); + } + } + }, parseCodestream: function JpxImage_parseCodestream(data, start, end) { var context = {}; try { var position = start; - while (position < end) { + while (position + 1 < end) { var code = readUint16(data, position); position += 2; - var length = 0, j; + var length = 0, j, sqcd, spqcds, spqcdSize, scalarExpounded, tile; switch (code) { case 0xFF4F: // Start of codestream (SOC) context.mainHeader = true; @@ -36362,8 +39425,7 @@ var JpxImage = (function JpxImageClosure() { length = readUint16(data, position); var qcd = {}; j = position + 2; - var sqcd = data[j++]; - var spqcdSize, scalarExpounded; + sqcd = data[j++]; switch (sqcd & 0x1F) { case 0: spqcdSize = 8; @@ -36383,7 +39445,7 @@ var JpxImage = (function JpxImageClosure() { qcd.noQuantization = (spqcdSize == 8); qcd.scalarExpounded = scalarExpounded; qcd.guardBits = sqcd >> 5; - var spqcds = []; + spqcds = []; while (j < length + position) { var spqcd = {}; if (spqcdSize == 8) { @@ -36415,8 +39477,7 @@ var JpxImage = (function JpxImageClosure() { cqcc = readUint16(data, j); j += 2; } - var sqcd = data[j++]; - var spqcdSize, scalarExpounded; + sqcd = data[j++]; switch (sqcd & 0x1F) { case 0: spqcdSize = 8; @@ -36436,9 +39497,9 @@ var JpxImage = (function JpxImageClosure() { qcc.noQuantization = (spqcdSize == 8); qcc.scalarExpounded = scalarExpounded; qcc.guardBits = sqcd >> 5; - var spqcds = []; + spqcds = []; while (j < (length + position)) { - var spqcd = {}; + spqcd = {}; if (spqcdSize == 8) { spqcd.epsilon = data[j++] >> 3; spqcd.mu = 0; @@ -36464,7 +39525,6 @@ var JpxImage = (function JpxImageClosure() { cod.entropyCoderWithCustomPrecincts = !!(scod & 1); cod.sopMarkerUsed = !!(scod & 2); cod.ephMarkerUsed = !!(scod & 4); - var codingStyle = {}; cod.progressionOrder = data[j++]; cod.layersCount = readUint16(data, j); j += 2; @@ -36480,7 +39540,7 @@ var JpxImage = (function JpxImageClosure() { cod.verticalyStripe = !!(blockStyle & 8); cod.predictableTermination = !!(blockStyle & 16); cod.segmentationSymbolUsed = !!(blockStyle & 32); - cod.transformation = data[j++]; + cod.reversibleTransformation = data[j++]; if (cod.entropyCoderWithCustomPrecincts) { var precinctsSizes = []; while (j < length + position) { @@ -36511,7 +39571,7 @@ var JpxImage = (function JpxImageClosure() { break; case 0xFF90: // Start of tile-part (SOT) length = readUint16(data, position); - var tile = {}; + tile = {}; tile.index = readUint16(data, position + 2); tile.length = readUint32(data, position + 4); tile.dataEnd = tile.length + position - 2; @@ -36529,7 +39589,7 @@ var JpxImage = (function JpxImageClosure() { context.currentTile = tile; break; case 0xFF93: // Start of data (SOD) - var tile = context.currentTile; + tile = context.currentTile; if (tile.partIndex === 0) { initializeTile(context, tile.index); buildPackets(context); @@ -36543,6 +39603,8 @@ var JpxImage = (function JpxImageClosure() { length = readUint16(data, position); // skipping content break; + case 0xFF53: // Coding style component (COC) + throw 'Codestream code 0xFF53 (COC) is not implemented'; default: throw 'Unknown codestream code: ' + code.toString(16); } @@ -36561,21 +39623,6 @@ var JpxImage = (function JpxImageClosure() { this.componentsCount = context.SIZ.Csiz; } }; - function readUint32(data, offset) { - return (data[offset] << 24) | (data[offset + 1] << 16) | - (data[offset + 2] << 8) | data[offset + 3]; - } - function readUint16(data, offset) { - return (data[offset] << 8) | data[offset + 1]; - } - function log2(x) { - var n = 1, i = 0; - while (x > n) { - n <<= 1; - i++; - } - return i; - } function calculateComponentDimensions(component, siz) { // Section B.2 Component mapping component.x0 = Math.ceil(siz.XOsiz / component.XRsiz); @@ -36588,12 +39635,12 @@ var JpxImage = (function JpxImageClosure() { function calculateTileGrids(context, components) { var siz = context.SIZ; // Section B.3 Division into tile and tile-components - var tiles = []; + var tile, tiles = []; var numXtiles = Math.ceil((siz.Xsiz - siz.XTOsiz) / siz.XTsiz); var numYtiles = Math.ceil((siz.Ysiz - siz.YTOsiz) / siz.YTsiz); for (var q = 0; q < numYtiles; q++) { for (var p = 0; p < numXtiles; p++) { - var tile = {}; + tile = {}; tile.tx0 = Math.max(siz.XTOsiz + p * siz.XTsiz, siz.XOsiz); tile.ty0 = Math.max(siz.YTOsiz + q * siz.YTsiz, siz.YOsiz); tile.tx1 = Math.min(siz.XTOsiz + (p + 1) * siz.XTsiz, siz.Xsiz); @@ -36609,9 +39656,9 @@ var JpxImage = (function JpxImageClosure() { var componentsCount = siz.Csiz; for (var i = 0, ii = componentsCount; i < ii; i++) { var component = components[i]; - var tileComponents = []; for (var j = 0, jj = tiles.length; j < jj; j++) { - var tileComponent = {}, tile = tiles[j]; + var tileComponent = {}; + tile = tiles[j]; tileComponent.tcx0 = Math.ceil(tile.tx0 / component.XRsiz); tileComponent.tcy0 = Math.ceil(tile.ty0 / component.YRsiz); tileComponent.tcx1 = Math.ceil(tile.tx1 / component.XRsiz); @@ -36670,16 +39717,17 @@ var JpxImage = (function JpxImageClosure() { var ycb_ = dimensions.ycb_; var codeblockWidth = 1 << xcb_; var codeblockHeight = 1 << ycb_; - var cbx0 = Math.floor(subband.tbx0 / codeblockWidth); - var cby0 = Math.floor(subband.tby0 / codeblockHeight); - var cbx1 = Math.ceil(subband.tbx1 / codeblockWidth); - var cby1 = Math.ceil(subband.tby1 / codeblockHeight); + var cbx0 = subband.tbx0 >> xcb_; + var cby0 = subband.tby0 >> ycb_; + var cbx1 = (subband.tbx1 + codeblockWidth - 1) >> xcb_; + var cby1 = (subband.tby1 + codeblockHeight - 1) >> ycb_; var precinctParameters = subband.resolution.precinctParameters; var codeblocks = []; var precincts = []; - for (var j = cby0; j < cby1; j++) { - for (var i = cbx0; i < cbx1; i++) { - var codeblock = { + var i, j, codeblock, precinctNumber; + for (j = cby0; j < cby1; j++) { + for (i = cbx0; i < cbx1; i++) { + codeblock = { cbx: i, cby: j, tbx0: codeblockWidth * i, @@ -36694,26 +39742,28 @@ var JpxImage = (function JpxImageClosure() { var pj = Math.floor((codeblock.tby0 - precinctParameters.precinctYOffset) / precinctParameters.precinctHeight); - var precinctNumber = pj + - pi * precinctParameters.numprecinctswide; + precinctNumber = pj + pi * precinctParameters.numprecinctswide; codeblock.tbx0_ = Math.max(subband.tbx0, codeblock.tbx0); codeblock.tby0_ = Math.max(subband.tby0, codeblock.tby0); codeblock.tbx1_ = Math.min(subband.tbx1, codeblock.tbx1); codeblock.tby1_ = Math.min(subband.tby1, codeblock.tby1); codeblock.precinctNumber = precinctNumber; codeblock.subbandType = subband.type; - var coefficientsLength = (codeblock.tbx1_ - codeblock.tbx0_) * - (codeblock.tby1_ - codeblock.tby0_); codeblock.Lblock = 3; codeblocks.push(codeblock); // building precinct for the sub-band - var precinct; - if (precinctNumber in precincts) { - precinct = precincts[precinctNumber]; - precinct.cbxMin = Math.min(precinct.cbxMin, i); - precinct.cbyMin = Math.min(precinct.cbyMin, j); - precinct.cbxMax = Math.max(precinct.cbxMax, i); - precinct.cbyMax = Math.max(precinct.cbyMax, j); + var precinct = precincts[precinctNumber]; + if (precinct !== undefined) { + if (i < precinct.cbxMin) { + precinct.cbxMin = i; + } else if (i > precinct.cbxMax) { + precinct.cbxMax = i; + } + if (j < precinct.cbyMin) { + precinct.cbxMin = j; + } else if (j > precinct.cbyMax) { + precinct.cbyMax = j; + } } else { precincts[precinctNumber] = precinct = { cbxMin: i, @@ -36732,10 +39782,6 @@ var JpxImage = (function JpxImageClosure() { numcodeblockhigh: cby1 - cby1 + 1 }; subband.codeblocks = codeblocks; - for (var i = 0, ii = codeblocks.length; i < ii; i++) { - var codeblock = codeblocks[i]; - var precinctNumber = codeblock.precinctNumber; - } subband.precincts = precincts; } function createPacket(resolution, precinctNumber, layerNumber) { @@ -36921,7 +39967,6 @@ var JpxImage = (function JpxImageClosure() { } // Generate the packets sequence var progressionOrder = tile.codingStyleDefaultParameters.progressionOrder; - var packetsIterator; switch (progressionOrder) { case 0: tile.packetsIterator = @@ -36965,24 +40010,22 @@ var JpxImage = (function JpxImageClosure() { } } function readCodingpasses() { - var value = readBits(1); - if (value === 0) { + if (readBits(1) === 0) { return 1; } - value = (value << 1) | readBits(1); - if (value == 0x02) { + if (readBits(1) === 0) { return 2; } - value = (value << 2) | readBits(2); - if (value <= 0x0E) { - return (value & 0x03) + 3; + var value = readBits(2); + if (value < 3) { + return value + 3; } - value = (value << 5) | readBits(5); - if (value <= 0x1FE) { - return (value & 0x1F) + 6; + value = readBits(5); + if (value < 31) { + return value + 6; } - value = (value << 7) | readBits(7); - return (value & 0x7F) + 37; + value = readBits(7); + return value + 37; } var tileIndex = context.currentTile.index; var tile = context.tiles[tileIndex]; @@ -36994,19 +40037,20 @@ var JpxImage = (function JpxImageClosure() { continue; } var layerNumber = packet.layerNumber; - var queue = []; + var queue = [], codeblock; for (var i = 0, ii = packet.codeblocks.length; i < ii; i++) { - var codeblock = packet.codeblocks[i]; + codeblock = packet.codeblocks[i]; var precinct = codeblock.precinct; var codeblockColumn = codeblock.cbx - precinct.cbxMin; var codeblockRow = codeblock.cby - precinct.cbyMin; var codeblockIncluded = false; var firstTimeInclusion = false; + var valueReady; if ('included' in codeblock) { codeblockIncluded = !!readBits(1); } else { // reading inclusion tree - var precinct = codeblock.precinct; + precinct = codeblock.precinct; var inclusionTree, zeroBitPlanesTree; if ('inclusionTree' in precinct) { inclusionTree = precinct.inclusionTree; @@ -37023,7 +40067,7 @@ var JpxImage = (function JpxImageClosure() { if (inclusionTree.reset(codeblockColumn, codeblockRow, layerNumber)) { while (true) { if (readBits(1)) { - var valueReady = !inclusionTree.nextLevel(); + valueReady = !inclusionTree.nextLevel(); if (valueReady) { codeblock.included = true; codeblockIncluded = firstTimeInclusion = true; @@ -37044,7 +40088,7 @@ var JpxImage = (function JpxImageClosure() { zeroBitPlanesTree.reset(codeblockColumn, codeblockRow); while (true) { if (readBits(1)) { - var valueReady = !zeroBitPlanesTree.nextLevel(); + valueReady = !zeroBitPlanesTree.nextLevel(); if (valueReady) { break; } @@ -37072,7 +40116,7 @@ var JpxImage = (function JpxImageClosure() { alignToByte(); while (queue.length > 0) { var packetItem = queue.shift(); - var codeblock = packetItem.codeblock; + codeblock = packetItem.codeblock; if (!('data' in codeblock)) { codeblock.data = []; } @@ -37087,9 +40131,15 @@ var JpxImage = (function JpxImageClosure() { } return position; } - function copyCoefficients(coefficients, x0, y0, width, height, - delta, mb, codeblocks, transformation, - segmentationSymbolUsed) { + function copyCoefficients(coefficients, levelWidth, levelHeight, subband, + delta, mb, reversible, segmentationSymbolUsed) { + var x0 = subband.tbx0; + var y0 = subband.tby0; + var width = subband.tbx1 - subband.tbx0; + var codeblocks = subband.codeblocks; + var right = subband.type.charAt(0) === 'H' ? 1 : 0; + var bottom = subband.type.charAt(1) === 'H' ? levelWidth : 0; + for (var i = 0, ii = codeblocks.length; i < ii; ++i) { var codeblock = codeblocks[i]; var blockWidth = codeblock.tbx1_ - codeblock.tbx0_; @@ -37103,28 +40153,30 @@ var JpxImage = (function JpxImageClosure() { var bitModel, currentCodingpassType; bitModel = new BitModel(blockWidth, blockHeight, codeblock.subbandType, - codeblock.zeroBitPlanes); + codeblock.zeroBitPlanes, mb); currentCodingpassType = 2; // first bit plane starts from cleanup // collect data var data = codeblock.data, totalLength = 0, codingpasses = 0; - for (var q = 0, qq = data.length; q < qq; q++) { - var dataItem = data[q]; + var j, jj, dataItem; + for (j = 0, jj = data.length; j < jj; j++) { + dataItem = data[j]; totalLength += dataItem.end - dataItem.start; codingpasses += dataItem.codingpasses; } - var encodedData = new Uint8Array(totalLength), k = 0; - for (var q = 0, qq = data.length; q < qq; q++) { - var dataItem = data[q]; + var encodedData = new Uint8Array(totalLength); + var position = 0; + for (j = 0, jj = data.length; j < jj; j++) { + dataItem = data[j]; var chunk = dataItem.data.subarray(dataItem.start, dataItem.end); - encodedData.set(chunk, k); - k += chunk.length; + encodedData.set(chunk, position); + position += chunk.length; } // decoding the item var decoder = new ArithmeticDecoder(encodedData, 0, totalLength); bitModel.setDecoder(decoder); - for (var q = 0; q < codingpasses; q++) { + for (j = 0; j < codingpasses; j++) { switch (currentCodingpassType) { case 0: bitModel.runSignificancePropogationPass(); @@ -37143,17 +40195,34 @@ var JpxImage = (function JpxImageClosure() { } var offset = (codeblock.tbx0_ - x0) + (codeblock.tby0_ - y0) * width; - var n, nb, correction, position = 0; - var irreversible = (transformation === TransformType.IRREVERSIBLE); var sign = bitModel.coefficentsSign; var magnitude = bitModel.coefficentsMagnitude; var bitsDecoded = bitModel.bitsDecoded; - for (var j = 0; j < blockHeight; j++) { - for (var k = 0; k < blockWidth; k++) { - n = (sign[position] ? -1 : 1) * magnitude[position]; - nb = bitsDecoded[position]; - correction = (irreversible || mb > nb) ? 1 << (mb - nb) : 1; - coefficients[offset++] = n * correction * delta; + var magnitudeCorrection = reversible ? 0 : 0.5; + var k, n, nb; + position = 0; + // Do the interleaving of Section F.3.3 here, so we do not need + // to copy later. LL level is not interleaved, just copied. + var interleave = (subband.type !== 'LL'); + for (j = 0; j < blockHeight; j++) { + var row = (offset / width) | 0; // row in the non-interleaved subband + var levelOffset = 2 * row * (levelWidth - width) + right + bottom; + for (k = 0; k < blockWidth; k++) { + n = magnitude[position]; + if (n !== 0) { + n = (n + magnitudeCorrection) * delta; + if (sign[position] !== 0) { + n = -n; + } + nb = bitsDecoded[position]; + var pos = interleave ? (levelOffset + (offset << 1)) : offset; + if (reversible && (nb >= mb)) { + coefficients[pos] = n; + } else { + coefficients[pos] = n * (1 << (mb - nb)); + } + } + offset++; position++; } offset += width - blockWidth; @@ -37169,19 +40238,23 @@ var JpxImage = (function JpxImageClosure() { var spqcds = quantizationParameters.SPqcds; var scalarExpounded = quantizationParameters.scalarExpounded; var guardBits = quantizationParameters.guardBits; - var transformation = codingStyleParameters.transformation; var segmentationSymbolUsed = codingStyleParameters.segmentationSymbolUsed; var precision = context.components[c].precision; - var transformation = codingStyleParameters.transformation; - var transform = (transformation === TransformType.IRREVERSIBLE ? - new IrreversibleTransform() : new ReversibleTransform()); + var reversible = codingStyleParameters.reversibleTransformation; + var transform = (reversible ? new ReversibleTransform() : + new IrreversibleTransform()); var subbandCoefficients = []; - var k = 0, b = 0; + var b = 0; for (var i = 0; i <= decompositionLevelsCount; i++) { var resolution = component.resolutions[i]; + var width = resolution.trx1 - resolution.trx0; + var height = resolution.try1 - resolution.try0; + // Allocate space for the whole sublevel. + var coefficients = new Float32Array(width * height); + for (var j = 0, jj = resolution.subbands.length; j < jj; j++) { var mu, epsilon; if (!scalarExpounded) { @@ -37191,31 +40264,30 @@ var JpxImage = (function JpxImageClosure() { } else { mu = spqcds[b].mu; epsilon = spqcds[b].epsilon; + b++; } var subband = resolution.subbands[j]; - var width = subband.tbx1 - subband.tbx0; - var height = subband.tby1 - subband.tby0; var gainLog2 = SubbandsGainLog2[subband.type]; // calulate quantization coefficient (Section E.1.1.1) - var delta = (transformation === TransformType.IRREVERSIBLE ? - Math.pow(2, precision + gainLog2 - epsilon) * (1 + mu / 2048) : 1); + var delta = (reversible ? 1 : + Math.pow(2, precision + gainLog2 - epsilon) * (1 + mu / 2048)); var mb = (guardBits + epsilon - 1); - var coefficients = new Float32Array(width * height); - copyCoefficients(coefficients, subband.tbx0, subband.tby0, - width, height, delta, mb, subband.codeblocks, transformation, - segmentationSymbolUsed); - - subbandCoefficients.push({ - width: width, - height: height, - items: coefficients - }); - - b++; + // In the first resolution level, copyCoefficients will fill the + // whole array with coefficients. In the succeding passes, + // copyCoefficients will consecutively fill in the values that belong + // to the interleaved positions of the HL, LH, and HH coefficients. + // The LL coefficients will then be interleaved in Transform.iterate(). + copyCoefficients(coefficients, width, height, subband, delta, mb, + reversible, segmentationSymbolUsed); } + subbandCoefficients.push({ + width: width, + height: height, + items: coefficients + }); } var result = transform.calculate(subbandCoefficients, @@ -37235,72 +40307,91 @@ var JpxImage = (function JpxImageClosure() { var resultImages = []; for (var i = 0, ii = context.tiles.length; i < ii; i++) { var tile = context.tiles[i]; - var result = []; - for (var c = 0; c < componentsCount; c++) { - var image = transformTile(context, tile, c); - result.push(image); + var transformedTiles = []; + var c; + for (c = 0; c < componentsCount; c++) { + transformedTiles[c] = transformTile(context, tile, c); } + var tile0 = transformedTiles[0]; + var out = new Uint8Array(tile0.items.length * componentsCount); + var result = { + left: tile0.left, + top: tile0.top, + width: tile0.width, + height: tile0.height, + items: out + }; // Section G.2.2 Inverse multi component transform + var shift, offset, max, min, maxK; + var pos = 0, j, jj, y0, y1, y2, r, g, b, k, val; if (tile.codingStyleDefaultParameters.multipleComponentTransform) { + var fourComponents = componentsCount === 4; + var y0items = transformedTiles[0].items; + var y1items = transformedTiles[1].items; + var y2items = transformedTiles[2].items; + var y3items = fourComponents ? transformedTiles[3].items : null; + + // HACK: The multiple component transform formulas below assume that + // all components have the same precision. With this in mind, we + // compute shift and offset only once. + shift = components[0].precision - 8; + offset = (128 << shift) + 0.5; + max = 255 * (1 << shift); + maxK = max * 0.5; + min = -maxK; + var component0 = tile.components[0]; - var transformation = component0.codingStyleParameters.transformation; - if (transformation === TransformType.IRREVERSIBLE) { + var alpha01 = componentsCount - 3; + jj = y0items.length; + if (!component0.codingStyleParameters.reversibleTransformation) { // inverse irreversible multiple component transform - var y0items = result[0].items; - var y1items = result[1].items; - var y2items = result[2].items; - for (var j = 0, jj = y0items.length; j < jj; ++j) { - var y0 = y0items[j], y1 = y1items[j], y2 = y2items[j]; - y0items[j] = y0 + 1.402 * y2 + 0.5; - y1items[j] = y0 - 0.34413 * y1 - 0.71414 * y2 + 0.5; - y2items[j] = y0 + 1.772 * y1 + 0.5; + for (j = 0; j < jj; j++, pos += alpha01) { + y0 = y0items[j] + offset; + y1 = y1items[j]; + y2 = y2items[j]; + r = y0 + 1.402 * y2; + g = y0 - 0.34413 * y1 - 0.71414 * y2; + b = y0 + 1.772 * y1; + out[pos++] = r <= 0 ? 0 : r >= max ? 255 : r >> shift; + out[pos++] = g <= 0 ? 0 : g >= max ? 255 : g >> shift; + out[pos++] = b <= 0 ? 0 : b >= max ? 255 : b >> shift; } } else { // inverse reversible multiple component transform - var y0items = result[0].items; - var y1items = result[1].items; - var y2items = result[2].items; - for (var j = 0, jj = y0items.length; j < jj; ++j) { - var y0 = y0items[j], y1 = y1items[j], y2 = y2items[j]; - var i1 = y0 - ((y2 + y1) >> 2); - y1items[j] = i1; - y0items[j] = y2 + i1; - y2items[j] = y1 + i1; + for (j = 0; j < jj; j++, pos += alpha01) { + y0 = y0items[j] + offset; + y1 = y1items[j]; + y2 = y2items[j]; + g = y0 - ((y2 + y1) >> 2); + r = g + y2; + b = g + y1; + out[pos++] = r <= 0 ? 0 : r >= max ? 255 : r >> shift; + out[pos++] = g <= 0 ? 0 : g >= max ? 255 : g >> shift; + out[pos++] = b <= 0 ? 0 : b >= max ? 255 : b >> shift; } } - } - - // Section G.1 DC level shifting to unsigned component values - for (var c = 0; c < componentsCount; c++) { - var component = components[c]; - if (component.isSigned) { - continue; - } - - var offset = 1 << (component.precision - 1); - var tileImage = result[c]; - var items = tileImage.items; - for (var j = 0, jj = items.length; j < jj; j++) { - items[j] += offset; + if (fourComponents) { + for (j = 0, pos = 3; j < jj; j++, pos += 4) { + k = y3items[j]; + out[pos] = k <= min ? 0 : k >= maxK ? 255 : (k + offset) >> shift; + } } - } - - // To simplify things: shift and clamp output to 8 bit unsigned - for (var c = 0; c < componentsCount; c++) { - var component = components[c]; - var offset = component.isSigned ? 128 : 0; - var shift = component.precision - 8; - var tileImage = result[c]; - var items = tileImage.items; - var data = new Uint8Array(items.length); - for (var j = 0, jj = items.length; j < jj; j++) { - var value = (items[j] >> shift) + offset; - data[j] = value < 0 ? 0 : value > 255 ? 255 : value; + } else { // no multi-component transform + for (c = 0; c < componentsCount; c++) { + var items = transformedTiles[c].items; + shift = components[c].precision - 8; + offset = (128 << shift) + 0.5; + max = (127.5 * (1 << shift)); + min = -max; + for (pos = c, j = 0, jj = items.length; j < jj; j++) { + val = items[j]; + out[pos] = val <= min ? 0 : + val >= max ? 255 : (val + offset) >> shift; + pos += componentsCount; + } } - result[c].items = data; } - resultImages.push(result); } return resultImages; @@ -37309,7 +40400,6 @@ var JpxImage = (function JpxImageClosure() { var siz = context.SIZ; var componentsCount = siz.Csiz; var tile = context.tiles[tileIndex]; - var resultTiles = []; for (var c = 0; c < componentsCount; c++) { var component = tile.components[c]; var qcdOrQcc = (c in context.currentTile.QCC ? @@ -37340,9 +40430,9 @@ var JpxImage = (function JpxImageClosure() { } TagTree.prototype = { reset: function TagTree_reset(i, j) { - var currentLevel = 0, value = 0; + var currentLevel = 0, value = 0, level; while (currentLevel < this.levels.length) { - var level = this.levels[currentLevel]; + level = this.levels[currentLevel]; var index = i + j * level.width; if (index in level.items) { value = level.items[index]; @@ -37354,7 +40444,7 @@ var JpxImage = (function JpxImageClosure() { currentLevel++; } currentLevel--; - var level = this.levels[currentLevel]; + level = this.levels[currentLevel]; level.items[level.index] = value; this.currentLevel = currentLevel; delete this.value; @@ -37374,7 +40464,7 @@ var JpxImage = (function JpxImageClosure() { } this.currentLevel = currentLevel; - var level = this.levels[currentLevel]; + level = this.levels[currentLevel]; level.items[level.index] = value; return true; } @@ -37440,7 +40530,7 @@ var JpxImage = (function JpxImageClosure() { var level = this.levels[levelIndex]; var currentValue = level.items[level.index]; while (--levelIndex >= 0) { - var level = this.levels[levelIndex]; + level = this.levels[levelIndex]; level.items[level.index] = currentValue; } }, @@ -37455,7 +40545,7 @@ var JpxImage = (function JpxImageClosure() { } this.currentLevel = currentLevel; - var level = this.levels[currentLevel]; + level = this.levels[currentLevel]; level.items[level.index] = value; return true; } @@ -37486,32 +40576,7 @@ var JpxImage = (function JpxImageClosure() { 8, 0, 8, 8, 8, 0, 8, 8, 8, 0, 0, 0, 0, 0, 8, 8, 8, 0, 8, 8, 8, 0, 8, 8, 8 ]); - // Table D-2 - function calcSignContribution(significance0, sign0, significance1, sign1) { - if (significance1) { - if (!sign1) { - return significance0 ? (!sign0 ? 1 : 0) : 1; - } else { - return significance0 ? (!sign0 ? 0 : -1) : -1; - } - } else { - return significance0 ? (!sign0 ? 1 : -1) : 0; - } - } - // Table D-3 - var SignContextLabels = [ - {contextLabel: 13, xorBit: 0}, - {contextLabel: 12, xorBit: 0}, - {contextLabel: 11, xorBit: 0}, - {contextLabel: 10, xorBit: 0}, - {contextLabel: 9, xorBit: 0}, - {contextLabel: 10, xorBit: 1}, - {contextLabel: 11, xorBit: 1}, - {contextLabel: 12, xorBit: 1}, - {contextLabel: 13, xorBit: 1} - ]; - - function BitModel(width, height, subband, zeroBitPlanes) { + function BitModel(width, height, subband, zeroBitPlanes, mb) { this.width = width; this.height = height; @@ -37524,12 +40589,16 @@ var JpxImage = (function JpxImageClosure() { // add border state cells for significanceState this.neighborsSignificance = new Uint8Array(coefficientCount); this.coefficentsSign = new Uint8Array(coefficientCount); - this.coefficentsMagnitude = new Uint32Array(coefficientCount); + this.coefficentsMagnitude = mb > 14 ? new Uint32Array(coefficientCount) : + mb > 6 ? new Uint16Array(coefficientCount) : + new Uint8Array(coefficientCount); this.processingFlags = new Uint8Array(coefficientCount); - var bitsDecoded = new Uint8Array(this.width * this.height); - for (var i = 0, ii = bitsDecoded.length; i < ii; i++) { - bitsDecoded[i] = zeroBitPlanes; + var bitsDecoded = new Uint8Array(coefficientCount); + if (zeroBitPlanes !== 0) { + for (var i = 0; i < coefficientCount; i++) { + bitsDecoded[i] = zeroBitPlanes; + } } this.bitsDecoded = bitsDecoded; @@ -37541,7 +40610,7 @@ var JpxImage = (function JpxImageClosure() { this.decoder = decoder; }, reset: function BitModel_reset() { - // We have 17 contexts that are accessed via context labels, + // We have 17 contexts that are accessed via context labels, // plus the uniform and runlength context. this.contexts = new Int8Array(19); @@ -37552,32 +40621,39 @@ var JpxImage = (function JpxImageClosure() { this.contexts[RUNLENGTH_CONTEXT] = (3 << 1) | 0; }, setNeighborsSignificance: - function BitModel_setNeighborsSignificance(row, column) { + function BitModel_setNeighborsSignificance(row, column, index) { var neighborsSignificance = this.neighborsSignificance; var width = this.width, height = this.height; - var index = row * width + column; + var left = (column > 0); + var right = (column + 1 < width); + var i; + if (row > 0) { - if (column > 0) { - neighborsSignificance[index - width - 1] += 0x10; + i = index - width; + if (left) { + neighborsSignificance[i - 1] += 0x10; } - if (column + 1 < width) { - neighborsSignificance[index - width + 1] += 0x10; + if (right) { + neighborsSignificance[i + 1] += 0x10; } - neighborsSignificance[index - width] += 0x04; + neighborsSignificance[i] += 0x04; } + if (row + 1 < height) { - if (column > 0) { - neighborsSignificance[index + width - 1] += 0x10; + i = index + width; + if (left) { + neighborsSignificance[i - 1] += 0x10; } - if (column + 1 < width) { - neighborsSignificance[index + width + 1] += 0x10; + if (right) { + neighborsSignificance[i + 1] += 0x10; } - neighborsSignificance[index + width] += 0x04; + neighborsSignificance[i] += 0x04; } - if (column > 0) { + + if (left) { neighborsSignificance[index - 1] += 0x01; } - if (column + 1 < width) { + if (right) { neighborsSignificance[index + 1] += 0x01; } neighborsSignificance[index] |= 0x80; @@ -37588,19 +40664,14 @@ var JpxImage = (function JpxImageClosure() { var width = this.width, height = this.height; var coefficentsMagnitude = this.coefficentsMagnitude; var coefficentsSign = this.coefficentsSign; - var contextLabels = this.contextLabels; var neighborsSignificance = this.neighborsSignificance; var processingFlags = this.processingFlags; var contexts = this.contexts; var labels = this.contextLabelTable; var bitsDecoded = this.bitsDecoded; - // clear processed flag var processedInverseMask = ~1; var processedMask = 1; var firstMagnitudeBitMask = 2; - for (var q = 0, qq = width * height; q < qq; q++) { - processingFlags[q] &= processedInverseMask; - } for (var i0 = 0; i0 < height; i0 += 4) { for (var j = 0; j < width; j++) { @@ -37610,6 +40681,8 @@ var JpxImage = (function JpxImageClosure() { if (i >= height) { break; } + // clear processed flag first + processingFlags[index] &= processedInverseMask; if (coefficentsMagnitude[index] || !neighborsSignificance[index]) { @@ -37619,10 +40692,10 @@ var JpxImage = (function JpxImageClosure() { var contextLabel = labels[neighborsSignificance[index]]; var decision = decoder.readBit(contexts, contextLabel); if (decision) { - var sign = this.decodeSignBit(i, j); + var sign = this.decodeSignBit(i, j, index); coefficentsSign[index] = sign; coefficentsMagnitude[index] = 1; - this.setNeighborsSignificance(i, j); + this.setNeighborsSignificance(i, j, index); processingFlags[index] |= firstMagnitudeBitMask; } bitsDecoded[index]++; @@ -37631,27 +40704,56 @@ var JpxImage = (function JpxImageClosure() { } } }, - decodeSignBit: function BitModel_decodeSignBit(row, column) { + decodeSignBit: function BitModel_decodeSignBit(row, column, index) { var width = this.width, height = this.height; - var index = row * width + column; var coefficentsMagnitude = this.coefficentsMagnitude; var coefficentsSign = this.coefficentsSign; - var horizontalContribution = calcSignContribution( - column > 0 && coefficentsMagnitude[index - 1], - coefficentsSign[index - 1], - column + 1 < width && coefficentsMagnitude[index + 1], - coefficentsSign[index + 1]); - var verticalContribution = calcSignContribution( - row > 0 && coefficentsMagnitude[index - width], - coefficentsSign[index - width], - row + 1 < height && coefficentsMagnitude[index + width], - coefficentsSign[index + width]); + var contribution, sign0, sign1, significance1; + var contextLabel, decoded; + + // calculate horizontal contribution + significance1 = (column > 0 && coefficentsMagnitude[index - 1] !== 0); + if (column + 1 < width && coefficentsMagnitude[index + 1] !== 0) { + sign1 = coefficentsSign[index + 1]; + if (significance1) { + sign0 = coefficentsSign[index - 1]; + contribution = 1 - sign1 - sign0; + } else { + contribution = 1 - sign1 - sign1; + } + } else if (significance1) { + sign0 = coefficentsSign[index - 1]; + contribution = 1 - sign0 - sign0; + } else { + contribution = 0; + } + var horizontalContribution = 3 * contribution; - var contextLabelAndXor = SignContextLabels[ - 3 * (1 - horizontalContribution) + (1 - verticalContribution)]; - var contextLabel = contextLabelAndXor.contextLabel; - var decoded = this.decoder.readBit(this.contexts, contextLabel); - return decoded ^ contextLabelAndXor.xorBit; + // calculate vertical contribution and combine with the horizontal + significance1 = (row > 0 && coefficentsMagnitude[index - width] !== 0); + if (row + 1 < height && coefficentsMagnitude[index + width] !== 0) { + sign1 = coefficentsSign[index + width]; + if (significance1) { + sign0 = coefficentsSign[index - width]; + contribution = 1 - sign1 - sign0 + horizontalContribution; + } else { + contribution = 1 - sign1 - sign1 + horizontalContribution; + } + } else if (significance1) { + sign0 = coefficentsSign[index - width]; + contribution = 1 - sign0 - sign0 + horizontalContribution; + } else { + contribution = horizontalContribution; + } + + if (contribution >= 0) { + contextLabel = 9 + contribution; + decoded = this.decoder.readBit(this.contexts, contextLabel); + } else { + contextLabel = 9 - contribution; + decoded = this.decoder.readBit(this.contexts, contextLabel) ^ 1; + } + return decoded; }, runMagnitudeRefinementPass: function BitModel_runMagnitudeRefinementPass() { @@ -37664,14 +40766,13 @@ var JpxImage = (function JpxImageClosure() { var processingFlags = this.processingFlags; var processedMask = 1; var firstMagnitudeBitMask = 2; - for (var i0 = 0; i0 < height; i0 += 4) { + var length = width * height; + var width4 = width * 4; + + for (var index0 = 0, indexNext; index0 < length; index0 = indexNext) { + indexNext = Math.min(length, index0 + width4); for (var j = 0; j < width; j++) { - for (var i1 = 0; i1 < 4; i1++) { - var i = i0 + i1; - if (i >= height) { - break; - } - var index = i * width + j; + for (var index = index0 + j; index < indexNext; index += width) { // significant but not those that have just become if (!coefficentsMagnitude[index] || @@ -37681,12 +40782,10 @@ var JpxImage = (function JpxImageClosure() { var contextLabel = 16; if ((processingFlags[index] & firstMagnitudeBitMask) !== 0) { - processingFlags[i * width + j] ^= firstMagnitudeBitMask; + processingFlags[index] ^= firstMagnitudeBitMask; // first refinement - var significance = neighborsSignificance[index]; - var sumOfSignificance = (significance & 3) + - ((significance >> 2) & 3) + ((significance >> 4) & 7); - contextLabel = sumOfSignificance >= 1 ? 15 : 14; + var significance = neighborsSignificance[index] & 127; + contextLabel = significance === 0 ? 15 : 14; } var bit = decoder.readBit(contexts, contextLabel); @@ -37702,7 +40801,6 @@ var JpxImage = (function JpxImageClosure() { var decoder = this.decoder; var width = this.width, height = this.height; var neighborsSignificance = this.neighborsSignificance; - var significanceState = this.significanceState; var coefficentsMagnitude = this.coefficentsMagnitude; var coefficentsSign = this.coefficentsSign; var contexts = this.contexts; @@ -37714,12 +40812,16 @@ var JpxImage = (function JpxImageClosure() { var oneRowDown = width; var twoRowsDown = width * 2; var threeRowsDown = width * 3; - for (var i0 = 0; i0 < height; i0 += 4) { + var iNext; + for (var i0 = 0; i0 < height; i0 = iNext) { + iNext = Math.min(i0 + 4, height); + var indexBase = i0 * width; + var checkAllEmpty = i0 + 3 < height; for (var j = 0; j < width; j++) { - var index0 = i0 * width + j; + var index0 = indexBase + j; // using the property: labels[neighborsSignificance[index]] == 0 // when neighborsSignificance[index] == 0 - var allEmpty = (i0 + 3 < height && + var allEmpty = (checkAllEmpty && processingFlags[index0] === 0 && processingFlags[index0 + oneRowDown] === 0 && processingFlags[index0 + twoRowsDown] === 0 && @@ -37729,7 +40831,7 @@ var JpxImage = (function JpxImageClosure() { neighborsSignificance[index0 + twoRowsDown] === 0 && neighborsSignificance[index0 + threeRowsDown] === 0); var i1 = 0, index = index0; - var i; + var i = i0, sign; if (allEmpty) { var hasSignificantCoefficent = decoder.readBit(contexts, RUNLENGTH_CONTEXT); @@ -37742,13 +40844,15 @@ var JpxImage = (function JpxImageClosure() { } i1 = (decoder.readBit(contexts, UNIFORM_CONTEXT) << 1) | decoder.readBit(contexts, UNIFORM_CONTEXT); - i = i0 + i1; - index += i1 * width; + if (i1 !== 0) { + i = i0 + i1; + index += i1 * width; + } - var sign = this.decodeSignBit(i, j); + sign = this.decodeSignBit(i, j, index); coefficentsSign[index] = sign; coefficentsMagnitude[index] = 1; - this.setNeighborsSignificance(i, j); + this.setNeighborsSignificance(i, j, index); processingFlags[index] |= firstMagnitudeBitMask; index = index0; @@ -37758,12 +40862,7 @@ var JpxImage = (function JpxImageClosure() { i1++; } - for (; i1 < 4; i1++, index += width) { - i = i0 + i1; - if (i >= height) { - break; - } - + for (i = i0 + i1; i < iNext; i++, index += width) { if (coefficentsMagnitude[index] || (processingFlags[index] & processedMask) !== 0) { continue; @@ -37771,11 +40870,11 @@ var JpxImage = (function JpxImageClosure() { var contextLabel = labels[neighborsSignificance[index]]; var decision = decoder.readBit(contexts, contextLabel); - if (decision == 1) { - var sign = this.decodeSignBit(i, j); + if (decision === 1) { + sign = this.decodeSignBit(i, j, index); coefficentsSign[index] = sign; coefficentsMagnitude[index] = 1; - this.setNeighborsSignificance(i, j); + this.setNeighborsSignificance(i, j, index); processingFlags[index] |= firstMagnitudeBitMask; } bitsDecoded[index]++; @@ -37806,9 +40905,8 @@ var JpxImage = (function JpxImageClosure() { Transform.prototype.calculate = function transformCalculate(subbands, u0, v0) { var ll = subbands[0]; - for (var i = 1, ii = subbands.length, j = 1; i < ii; i += 3, j++) { - ll = this.iterate(ll, subbands[i], subbands[i + 1], - subbands[i + 2], u0, v0); + for (var i = 1, ii = subbands.length; i < ii; i++) { + ll = this.iterate(ll, subbands[i], u0, v0); } return ll; }; @@ -37825,63 +40923,46 @@ var JpxImage = (function JpxImageClosure() { buffer[i1] = buffer[j1]; buffer[j2] = buffer[i2]; }; - Transform.prototype.iterate = function Transform_iterate(ll, hl, lh, hh, - u0, v0) { + Transform.prototype.iterate = function Transform_iterate(ll, hl_lh_hh, + u0, v0) { var llWidth = ll.width, llHeight = ll.height, llItems = ll.items; - var hlWidth = hl.width, hlHeight = hl.height, hlItems = hl.items; - var lhWidth = lh.width, lhHeight = lh.height, lhItems = lh.items; - var hhWidth = hh.width, hhHeight = hh.height, hhItems = hh.items; + var width = hl_lh_hh.width; + var height = hl_lh_hh.height; + var items = hl_lh_hh.items; + var i, j, k, l, u, v; - // Section F.3.3 interleave - var width = llWidth + hlWidth; - var height = llHeight + lhHeight; - var items = new Float32Array(width * height); - var i, j, k, l; - - for (i = 0; i < llHeight; i++) { - var k = i * llWidth, l = i * 2 * width; - for (var j = 0; j < llWidth; j++, k++, l += 2) { + // Interleave LL according to Section F.3.3 + for (k = 0, i = 0; i < llHeight; i++) { + l = i * 2 * width; + for (j = 0; j < llWidth; j++, k++, l += 2) { items[l] = llItems[k]; } } - for (i = 0; i < hlHeight; i++) { - k = i * hlWidth; l = i * 2 * width + 1; - for (j = 0; j < hlWidth; j++, k++, l += 2) { - items[l] = hlItems[k]; - } - } - for (i = 0; i < lhHeight; i++) { - k = i * lhWidth; l = (i * 2 + 1) * width; - for (j = 0; j < lhWidth; j++, k++, l += 2) { - items[l] = lhItems[k]; - } - } - for (i = 0; i < hhHeight; i++) { - k = i * hhWidth; l = (i * 2 + 1) * width + 1; - for (j = 0; j < hhWidth; j++, k++, l += 2) { - items[l] = hhItems[k]; - } - } + // The LL band is not needed anymore. + llItems = ll.items = null; var bufferPadding = 4; var rowBuffer = new Float32Array(width + 2 * bufferPadding); // Section F.3.4 HOR_SR - for (var v = 0; v < height; v++) { - if (width == 1) { - // if width = 1, when u0 even keep items as is, when odd divide by 2 - if ((u0 % 1) !== 0) { - items[v * width] /= 2; + if (width === 1) { + // if width = 1, when u0 even keep items as is, when odd divide by 2 + if ((u0 & 1) !== 0) { + for (v = 0, k = 0; v < height; v++, k += width) { + items[k] *= 0.5; } - continue; } - k = v * width; - rowBuffer.set(items.subarray(k, k + width), bufferPadding); + } else { + for (v = 0, k = 0; v < height; v++, k += width) { + rowBuffer.set(items.subarray(k, k + width), bufferPadding); - this.extend(rowBuffer, bufferPadding, width); - this.filter(rowBuffer, bufferPadding, width, u0, rowBuffer); + this.extend(rowBuffer, bufferPadding, width); + this.filter(rowBuffer, bufferPadding, width); - items.set(rowBuffer.subarray(bufferPadding, bufferPadding + width), k); + items.set( + rowBuffer.subarray(bufferPadding, bufferPadding + width), + k); + } } // Accesses to the items array can take long, because it may not fit into @@ -37895,40 +40976,42 @@ var JpxImage = (function JpxImageClosure() { for (i = 0; i < numBuffers; i++) { colBuffers.push(new Float32Array(height + 2 * bufferPadding)); } - var b, currentBuffer = 0, ll = bufferPadding + height; + var b, currentBuffer = 0; + ll = bufferPadding + height; // Section F.3.5 VER_SR - for (var u = 0; u < width; u++) { - if (height == 1) { + if (height === 1) { // if height = 1, when v0 even keep items as is, when odd divide by 2 - if ((v0 % 1) !== 0) { - items[u] /= 2; + if ((v0 & 1) !== 0) { + for (u = 0; u < width; u++) { + items[u] *= 0.5; } - continue; } - - // if we ran out of buffers, copy several image columns at once - if (currentBuffer === 0) { - numBuffers = Math.min(width - u, numBuffers); - for (k = u, l = bufferPadding; l < ll; k += width, l++) { - for (b = 0; b < numBuffers; b++) { - colBuffers[b][l] = items[k + b]; + } else { + for (u = 0; u < width; u++) { + // if we ran out of buffers, copy several image columns at once + if (currentBuffer === 0) { + numBuffers = Math.min(width - u, numBuffers); + for (k = u, l = bufferPadding; l < ll; k += width, l++) { + for (b = 0; b < numBuffers; b++) { + colBuffers[b][l] = items[k + b]; + } } + currentBuffer = numBuffers; } - currentBuffer = numBuffers; - } - currentBuffer--; - var buffer = colBuffers[currentBuffer]; - this.extend(buffer, bufferPadding, height); - this.filter(buffer, bufferPadding, height, v0, buffer); + currentBuffer--; + var buffer = colBuffers[currentBuffer]; + this.extend(buffer, bufferPadding, height); + this.filter(buffer, bufferPadding, height); - // If this is last buffer in this group of buffers, flush all buffers. - if (currentBuffer === 0) { - k = u - numBuffers + 1; - for (l = bufferPadding; l < ll; k += width, l++) { - for (b = 0; b < numBuffers; b++) { - items[k + b] = colBuffers[b][l]; + // If this is last buffer in this group of buffers, flush all buffers. + if (currentBuffer === 0) { + k = u - numBuffers + 1; + for (l = bufferPadding; l < ll; k += width, l++) { + for (b = 0; b < numBuffers; b++) { + items[k + b] = colBuffers[b][l]; + } } } } @@ -37951,10 +41034,10 @@ var JpxImage = (function JpxImageClosure() { IrreversibleTransform.prototype = Object.create(Transform.prototype); IrreversibleTransform.prototype.filter = - function irreversibleTransformFilter(y, offset, length, i0, x) { - var i0_ = Math.floor(i0 / 2); - var i1_ = Math.floor((i0 + length) / 2); - var offset_ = offset - (i0 % 1); + function irreversibleTransformFilter(x, offset, length) { + var len = length >> 1; + offset = offset | 0; + var j, n, current, next; var alpha = -1.586134342059924; var beta = -0.052980118572961; @@ -37963,40 +41046,74 @@ var JpxImage = (function JpxImageClosure() { var K = 1.230174104914001; var K_ = 1 / K; - // step 1 - var j = offset_ - 2; - for (var n = i0_ - 1, nn = i1_ + 2; n < nn; n++, j += 2) { - x[j] = K * y[j]; - } + // step 1 is combined with step 3 // step 2 - var j = offset_ - 3; - for (var n = i0_ - 2, nn = i1_ + 2; n < nn; n++, j += 2) { - x[j] = K_ * y[j]; + j = offset - 3; + for (n = len + 4; n--; j += 2) { + x[j] *= K_; } - // step 3 - var j = offset_ - 2; - for (var n = i0_ - 1, nn = i1_ + 2; n < nn; n++, j += 2) { - x[j] -= delta * (x[j - 1] + x[j + 1]); + // step 1 & 3 + j = offset - 2; + current = delta * x[j -1]; + for (n = len + 3; n--; j += 2) { + next = delta * x[j + 1]; + x[j] = K * x[j] - current - next; + if (n--) { + j += 2; + current = delta * x[j + 1]; + x[j] = K * x[j] - current - next; + } else { + break; + } } // step 4 - var j = offset_ - 1; - for (var n = i0_ - 1, nn = i1_ + 1; n < nn; n++, j += 2) { - x[j] -= gamma * (x[j - 1] + x[j + 1]); + j = offset - 1; + current = gamma * x[j - 1]; + for (n = len + 2; n--; j += 2) { + next = gamma * x[j + 1]; + x[j] -= current + next; + if (n--) { + j += 2; + current = gamma * x[j + 1]; + x[j] -= current + next; + } else { + break; + } } // step 5 - var j = offset_; - for (var n = i0_, nn = i1_ + 1; n < nn; n++, j += 2) { - x[j] -= beta * (x[j - 1] + x[j + 1]); + j = offset; + current = beta * x[j - 1]; + for (n = len + 1; n--; j += 2) { + next = beta * x[j + 1]; + x[j] -= current + next; + if (n--) { + j += 2; + current = beta * x[j + 1]; + x[j] -= current + next; + } else { + break; + } } // step 6 - var j = offset_ + 1; - for (var n = i0_, nn = i1_; n < nn; n++, j += 2) { - x[j] -= alpha * (x[j - 1] + x[j + 1]); + if (len !== 0) { + j = offset + 1; + current = alpha * x[j - 1]; + for (n = len; n--; j += 2) { + next = alpha * x[j + 1]; + x[j] -= current + next; + if (n--) { + j += 2; + current = alpha * x[j + 1]; + x[j] -= current + next; + } else { + break; + } + } } }; @@ -38011,17 +41128,17 @@ var JpxImage = (function JpxImageClosure() { ReversibleTransform.prototype = Object.create(Transform.prototype); ReversibleTransform.prototype.filter = - function reversibleTransformFilter(y, offset, length, i0, x) { - var i0_ = Math.floor(i0 / 2); - var i1_ = Math.floor((i0 + length) / 2); - var offset_ = offset - (i0 % 1); + function reversibleTransformFilter(x, offset, length) { + var len = length >> 1; + offset = offset | 0; + var j, n; - for (var n = i0_, nn = i1_ + 1, j = offset_; n < nn; n++, j += 2) { - x[j] = y[j] - Math.floor((y[j - 1] + y[j + 1] + 2) / 4); + for (j = offset, n = len + 1; n--; j += 2) { + x[j] -= (x[j - 1] + x[j + 1] + 2) >> 2; } - for (var n = i0_, nn = i1_, j = offset_ + 1; n < nn; n++, j += 2) { - x[j] = y[j] + Math.floor((x[j - 1] + x[j + 1]) / 2); + for (j = offset + 1, n = len; n--; j += 2) { + x[j] += (x[j - 1] + x[j + 1]) >> 1; } }; @@ -38042,7 +41159,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { if (id in this) { return this[id]; } - return (this[id] = new Int8Array(1<<16)); + return (this[id] = new Int8Array(1 << 16)); } }; @@ -38067,69 +41184,32 @@ var Jbig2Image = (function Jbig2ImageClosure() { // A.2 Procedure for decoding values function decodeInteger(contextCache, procedure, decoder) { var contexts = contextCache.getContexts(procedure); - var prev = 1; - var state = 1, v = 0, s; - var toRead = 32, offset = 4436; // defaults for state 7 - while (state) { - var bit = decoder.readBit(contexts, prev); - prev = (prev < 256 ? (prev << 1) | bit : - (((prev << 1) | bit) & 511) | 256); - switch (state) { - case 1: - s = !!bit; - break; - case 2: - if (bit) { - break; - } - state = 7; - toRead = 2; - offset = 0; - break; - case 3: - if (bit) { - break; - } - state = 7; - toRead = 4; - offset = 4; - break; - case 4: - if (bit) { - break; - } - state = 7; - toRead = 6; - offset = 20; - break; - case 5: - if (bit) { - break; - } - state = 7; - toRead = 8; - offset = 84; - break; - case 6: - if (bit) { - break; - } - state = 7; - toRead = 12; - offset = 340; - break; - default: - v = ((v << 1) | bit) >>> 0; - if (--toRead === 0) { - state = 0; - } - continue; + + function readBits(length) { + var v = 0; + for (var i = 0; i < length; i++) { + var bit = decoder.readBit(contexts, prev); + prev = (prev < 256 ? (prev << 1) | bit : + (((prev << 1) | bit) & 511) | 256); + v = (v << 1) | bit; } - state++; + return v >>> 0; } - v += offset; - return (!s ? v : (v > 0 ? -v : null)); + + var sign = readBits(1); + var value = readBits(1) ? + (readBits(1) ? + (readBits(1) ? + (readBits(1) ? + (readBits(1) ? + (readBits(32) + 4436) : + readBits(12) + 340) : + readBits(8) + 84) : + readBits(6) + 20) : + readBits(4) + 4) : + readBits(2); + return (sign === 0 ? value : (value > 0 ? -value : null)); } // A.3 The IAID decoding procedure @@ -38203,33 +41283,6 @@ var Jbig2Image = (function Jbig2ImageClosure() { 0x0008 // '0000' + '001000' ]; - function log2(x) { - var n = 1, i = 0; - while (x > n) { - n <<= 1; - i++; - } - return i; - } - - function readInt32(data, start) { - return (data[start] << 24) | (data[start + 1] << 16) | - (data[start + 2] << 8) | data[start + 3]; - } - - function readUint32(data, start) { - var value = readInt32(data, start); - return value & 0x80000000 ? (value + 4294967296) : value; - } - - function readUint16(data, start) { - return (data[start] << 8) | data[start + 1]; - } - - function readInt8(data, start) { - return (data[start] << 24) >> 24; - } - // 6.2 Generic Region Decoding Procedure function decodeBitmap(mmr, width, height, templateIndex, prediction, skip, at, decodingContext) { @@ -38252,8 +41305,9 @@ var Jbig2Image = (function Jbig2ImageClosure() { var templateY = new Int8Array(templateLength); var changingTemplateEntries = []; var reuseMask = 0, minX = 0, maxX = 0, minY = 0; + var c, k; - for (var k = 0; k < templateLength; k++) { + for (k = 0; k < templateLength; k++) { templateX[k] = template[k].x; templateY[k] = template[k].y; minX = Math.min(minX, template[k].x); @@ -38275,7 +41329,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { var changingTemplateX = new Int8Array(changingEntriesLength); var changingTemplateY = new Int8Array(changingEntriesLength); var changingTemplateBit = new Uint16Array(changingEntriesLength); - for (var c = 0; c < changingEntriesLength; c++) { + for (c = 0; c < changingEntriesLength; c++) { k = changingTemplateEntries[c]; changingTemplateX[c] = template[k].x; changingTemplateY[c] = template[k].y; @@ -38294,7 +41348,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { var decoder = decodingContext.decoder; var contexts = decodingContext.contextCache.getContexts('GB'); - var ltp = 0, c, j, i0, j0, k, contextLabel = 0, bit, shift; + var ltp = 0, j, i0, j0, contextLabel = 0, bit, shift; for (var i = 0; i < height; i++) { if (prediction) { var sltp = decoder.readBit(contexts, pseudoPixelContext); @@ -38361,7 +41415,8 @@ var Jbig2Image = (function Jbig2ImageClosure() { var codingTemplateLength = codingTemplate.length; var codingTemplateX = new Int32Array(codingTemplateLength); var codingTemplateY = new Int32Array(codingTemplateLength); - for (var k = 0; k < codingTemplateLength; k++) { + var k; + for (k = 0; k < codingTemplateLength; k++) { codingTemplateX[k] = codingTemplate[k].x; codingTemplateY[k] = codingTemplate[k].y; } @@ -38373,7 +41428,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { var referenceTemplateLength = referenceTemplate.length; var referenceTemplateX = new Int32Array(referenceTemplateLength); var referenceTemplateY = new Int32Array(referenceTemplateLength); - for (var k = 0; k < referenceTemplateLength; k++) { + for (k = 0; k < referenceTemplateLength; k++) { referenceTemplateX[k] = referenceTemplate[k].x; referenceTemplateY[k] = referenceTemplate[k].y; } @@ -38398,19 +41453,20 @@ var Jbig2Image = (function Jbig2ImageClosure() { var row = new Uint8Array(width); bitmap.push(row); for (var j = 0; j < width; j++) { - + var i0, j0; var contextLabel = 0; - for (var k = 0; k < codingTemplateLength; k++) { - var i0 = i + codingTemplateY[k], j0 = j + codingTemplateX[k]; + for (k = 0; k < codingTemplateLength; k++) { + i0 = i + codingTemplateY[k]; + j0 = j + codingTemplateX[k]; if (i0 < 0 || j0 < 0 || j0 >= width) { contextLabel <<= 1; // out of bound pixel } else { contextLabel = (contextLabel << 1) | bitmap[i0][j0]; } } - for (var k = 0; k < referenceTemplateLength; k++) { - var i0 = i + referenceTemplateY[k] + offsetY; - var j0 = j + referenceTemplateX[k] + offsetX; + for (k = 0; k < referenceTemplateLength; k++) { + i0 = i + referenceTemplateY[k] + offsetY; + j0 = j + referenceTemplateX[k] + offsetX; if (i0 < 0 || i0 >= referenceHeight || j0 < 0 || j0 >= referenceWidth) { contextLabel <<= 1; // out of bound pixel @@ -38460,16 +41516,28 @@ var Jbig2Image = (function Jbig2ImageClosure() { // 6.5.8.2 Refinement/aggregate-coded symbol bitmap var numberOfInstances = decodeInteger(contextCache, 'IAAI', decoder); if (numberOfInstances > 1) { - error('JBIG2 error: number of instances > 1 is not supported'); - } - var symbolId = decodeIAID(contextCache, decoder, symbolCodeLength); - var rdx = decodeInteger(contextCache, 'IARDX', decoder); // 6.4.11.3 - var rdy = decodeInteger(contextCache, 'IARDY', decoder); // 6.4.11.4 - var symbol = (symbolId < symbols.length ? symbols[symbolId] : - newSymbols[symbolId - symbols.length]); - bitmap = decodeRefinement(currentWidth, currentHeight, + bitmap = decodeTextRegion(huffman, refinement, + currentWidth, currentHeight, 0, + numberOfInstances, 1, //strip size + symbols.concat(newSymbols), + symbolCodeLength, + 0, //transposed + 0, //ds offset + 1, //top left 7.4.3.1.1 + 0, //OR operator + huffmanTables, + refinementTemplateIndex, refinementAt, + decodingContext); + } else { + var symbolId = decodeIAID(contextCache, decoder, symbolCodeLength); + var rdx = decodeInteger(contextCache, 'IARDX', decoder); // 6.4.11.3 + var rdy = decodeInteger(contextCache, 'IARDY', decoder); // 6.4.11.4 + var symbol = (symbolId < symbols.length ? symbols[symbolId] : + newSymbols[symbolId - symbols.length]); + bitmap = decodeRefinement(currentWidth, currentHeight, refinementTemplateIndex, symbol, rdx, rdy, false, refinementAt, decodingContext); + } } else { // 6.5.8.1 Direct-coded symbol bitmap bitmap = decodeBitmap(false, currentWidth, currentHeight, @@ -38515,8 +41583,9 @@ var Jbig2Image = (function Jbig2ImageClosure() { // Prepare bitmap var bitmap = []; - for (var i = 0; i < height; i++) { - var row = new Uint8Array(width); + var i, row; + for (i = 0; i < height; i++) { + row = new Uint8Array(width); if (defaultPixelValue) { for (var j = 0; j < width; j++) { row[j] = defaultPixelValue; @@ -38529,7 +41598,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { var contextCache = decodingContext.contextCache; var stripT = -decodeInteger(contextCache, 'IADT', decoder); // 6.4.6 var firstS = 0; - var i = 0; + i = 0; while (i < numberOfSymbolInstances) { var deltaT = decodeInteger(contextCache, 'IADT', decoder); // 6.4.6 stripT += deltaT; @@ -38538,12 +41607,12 @@ var Jbig2Image = (function Jbig2ImageClosure() { firstS += deltaFirstS; var currentS = firstS; do { - var currentT = stripSize == 1 ? 0 : - decodeInteger(contextCache, 'IAIT', decoder); // 6.4.9 + var currentT = (stripSize == 1 ? 0 : + decodeInteger(contextCache, 'IAIT', decoder)); // 6.4.9 var t = stripSize * stripT + currentT; var symbolId = decodeIAID(contextCache, decoder, symbolCodeLength); - var applyRefinement = refinement && - decodeInteger(contextCache, 'IARI', decoder); + var applyRefinement = (refinement && + decodeInteger(contextCache, 'IARI', decoder)); var symbolBitmap = inputSymbols[symbolId]; var symbolWidth = symbolBitmap[0].length; var symbolHeight = symbolBitmap.length; @@ -38561,25 +41630,26 @@ var Jbig2Image = (function Jbig2ImageClosure() { } var offsetT = t - ((referenceCorner & 1) ? 0 : symbolHeight); var offsetS = currentS - ((referenceCorner & 2) ? symbolWidth : 0); + var s2, t2, symbolRow; if (transposed) { // Place Symbol Bitmap from T1,S1 - for (var s2 = 0; s2 < symbolHeight; s2++) { - var row = bitmap[offsetS + s2]; + for (s2 = 0; s2 < symbolHeight; s2++) { + row = bitmap[offsetS + s2]; if (!row) { continue; } - var symbolRow = symbolBitmap[s2]; + symbolRow = symbolBitmap[s2]; // To ignore Parts of Symbol bitmap which goes // outside bitmap region var maxWidth = Math.min(width - offsetT, symbolWidth); switch (combinationOperator) { case 0: // OR - for (var t2 = 0; t2 < maxWidth; t2++) { + for (t2 = 0; t2 < maxWidth; t2++) { row[offsetT + t2] |= symbolRow[t2]; } break; case 2: // XOR - for (var t2 = 0; t2 < maxWidth; t2++) { + for (t2 = 0; t2 < maxWidth; t2++) { row[offsetT + t2] ^= symbolRow[t2]; } break; @@ -38590,20 +41660,20 @@ var Jbig2Image = (function Jbig2ImageClosure() { } currentS += symbolHeight - 1; } else { - for (var t2 = 0; t2 < symbolHeight; t2++) { - var row = bitmap[offsetT + t2]; + for (t2 = 0; t2 < symbolHeight; t2++) { + row = bitmap[offsetT + t2]; if (!row) { continue; } - var symbolRow = symbolBitmap[t2]; + symbolRow = symbolBitmap[t2]; switch (combinationOperator) { case 0: // OR - for (var s2 = 0; s2 < symbolWidth; s2++) { + for (s2 = 0; s2 < symbolWidth; s2++) { row[offsetS + s2] |= symbolRow[s2]; } break; case 2: // XOR - for (var s2 = 0; s2 < symbolWidth; s2++) { + for (s2 = 0; s2 < symbolWidth; s2++) { row[offsetS + s2] ^= symbolRow[s2]; } break; @@ -38643,7 +41713,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { var retainBits = [referredFlags & 31]; var position = start + 6; if (referredFlags == 7) { - referredToCount = readInt32(data, position - 1) & 0x1FFFFFFF; + referredToCount = readUint32(data, position - 1) & 0x1FFFFFFF; position += 3; var bytes = (referredToCount + 7) >> 3; retainBits[0] = data[position++]; @@ -38658,7 +41728,8 @@ var Jbig2Image = (function Jbig2ImageClosure() { var referredToSegmentNumberSize = (segmentHeader.number <= 256 ? 1 : (segmentHeader.number <= 65536 ? 2 : 4)); var referredTo = []; - for (var i = 0; i < referredToCount; i++) { + var i, ii; + for (i = 0; i < referredToCount; i++) { var number = (referredToSegmentNumberSize == 1 ? data[position] : (referredToSegmentNumberSize == 2 ? readUint16(data, position) : readUint32(data, position))); @@ -38693,7 +41764,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { searchPattern[3] = (genericRegionInfo.height >> 16) & 0xFF; searchPattern[4] = (genericRegionInfo.height >> 8) & 0xFF; searchPattern[5] = genericRegionInfo.height & 0xFF; - for (var i = position, ii = data.length; i < ii; i++) { + for (i = position, ii = data.length; i < ii; i++) { var j = 0; while (j < searchPatternLength && searchPattern[j] === data[i + j]) { j++; @@ -38760,7 +41831,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { var header = segment.header; var data = segment.data, position = segment.start, end = segment.end; - var args; + var args, at, i, atLength; switch (header.type) { case 0: // SymbolDictionary // 7.4.2 Symbol dictionary segment syntax @@ -38778,9 +41849,9 @@ var Jbig2Image = (function Jbig2ImageClosure() { dictionary.refinementTemplate = (dictionaryFlags >> 12) & 1; position += 2; if (!dictionary.huffman) { - var atLength = dictionary.template === 0 ? 4 : 1; - var at = []; - for (var i = 0; i < atLength; i++) { + atLength = dictionary.template === 0 ? 4 : 1; + at = []; + for (i = 0; i < atLength; i++) { at.push({ x: readInt8(data, position), y: readInt8(data, position + 1) @@ -38790,8 +41861,8 @@ var Jbig2Image = (function Jbig2ImageClosure() { dictionary.at = at; } if (dictionary.refinement && !dictionary.refinementTemplate) { - var at = []; - for (var i = 0; i < 2; i++) { + at = []; + for (i = 0; i < 2; i++) { at.push({ x: readInt8(data, position), y: readInt8(data, position + 1) @@ -38837,8 +41908,8 @@ var Jbig2Image = (function Jbig2ImageClosure() { !!(textRegionHuffmanFlags & 14); } if (textRegion.refinement && !textRegion.refinementTemplate) { - var at = []; - for (var i = 0; i < 2; i++) { + at = []; + for (i = 0; i < 2; i++) { at.push({ x: readInt8(data, position), y: readInt8(data, position + 1) @@ -38865,9 +41936,9 @@ var Jbig2Image = (function Jbig2ImageClosure() { genericRegion.template = (genericRegionSegmentFlags >> 1) & 3; genericRegion.prediction = !!(genericRegionSegmentFlags & 8); if (!genericRegion.mmr) { - var atLength = genericRegion.template === 0 ? 4 : 1; - var at = []; - for (var i = 0; i < atLength; i++) { + atLength = genericRegion.template === 0 ? 4 : 1; + at = []; + for (i = 0; i < atLength; i++) { at.push({ x: readInt8(data, position), y: readInt8(data, position + 1) @@ -38979,12 +42050,13 @@ var Jbig2Image = (function Jbig2ImageClosure() { var buffer = this.buffer; var mask0 = 128 >> (regionInfo.x & 7); var offset0 = regionInfo.y * rowSize + (regionInfo.x >> 3); + var i, j, mask, offset; switch (combinationOperator) { case 0: // OR - for (var i = 0; i < height; i++) { - var mask = mask0; - var offset = offset0; - for (var j = 0; j < width; j++) { + for (i = 0; i < height; i++) { + mask = mask0; + offset = offset0; + for (j = 0; j < width; j++) { if (bitmap[i][j]) { buffer[offset] |= mask; } @@ -38998,10 +42070,10 @@ var Jbig2Image = (function Jbig2ImageClosure() { } break; case 2: // XOR - for (var i = 0; i < height; i++) { - var mask = mask0; - var offset = offset0; - for (var j = 0; j < width; j++) { + for (i = 0; i < height; i++) { + mask = mask0; + offset = offset0; + for (j = 0; j < width; j++) { if (bitmap[i][j]) { buffer[offset] ^= mask; } @@ -39163,7 +42235,6 @@ var bidi = PDFJS.bidi = (function bidiClosure() { } function findUnequal(arr, start, value) { - var j; for (var j = start, jj = arr.length; j < jj; ++j) { if (arr[j] != value) { return j; @@ -39186,64 +42257,32 @@ var bidi = PDFJS.bidi = (function bidiClosure() { } } - function mirrorGlyphs(c) { - /* - # BidiMirroring-1.txt - 0028; 0029 # LEFT PARENTHESIS - 0029; 0028 # RIGHT PARENTHESIS - 003C; 003E # LESS-THAN SIGN - 003E; 003C # GREATER-THAN SIGN - 005B; 005D # LEFT SQUARE BRACKET - 005D; 005B # RIGHT SQUARE BRACKET - 007B; 007D # LEFT CURLY BRACKET - 007D; 007B # RIGHT CURLY BRACKET - 00AB; 00BB # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK - 00BB; 00AB # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK - */ - switch (c) { - case '(': - return ')'; - case ')': - return '('; - case '<': - return '>'; - case '>': - return '<'; - case ']': - return '['; - case '[': - return ']'; - case '}': - return '{'; - case '{': - return '}'; - case '\u00AB': - return '\u00BB'; - case '\u00BB': - return '\u00AB'; - default: - return c; - } + function createBidiText(str, isLTR, vertical) { + return { + str: str, + dir: (vertical ? 'ttb' : (isLTR ? 'ltr' : 'rtl')) + }; } - function BidiResult(str, isLTR, vertical) { - this.str = str; - this.dir = (vertical ? 'ttb' : (isLTR ? 'ltr' : 'rtl')); - } + // These are used in bidi(), which is called frequently. We re-use them on + // each call to avoid unnecessary allocations. + var chars = []; + var types = []; function bidi(str, startLevel, vertical) { var isLTR = true; var strLength = str.length; if (strLength === 0 || vertical) { - return new BidiResult(str, isLTR, vertical); + return createBidiText(str, isLTR, vertical); } // Get types and fill arrays - var chars = []; - var types = []; + chars.length = 0; + types.length = 0; var numBidi = 0; - for (var i = 0; i < strLength; ++i) { + var i, ii; + for (i = 0; i < strLength; ++i) { chars[i] = str.charAt(i); var charCode = str.charCodeAt(i); @@ -39269,7 +42308,7 @@ var bidi = PDFJS.bidi = (function bidiClosure() { // - If more than 30% chars are rtl then string is primarily rtl if (numBidi === 0) { isLTR = true; - return new BidiResult(str, isLTR); + return createBidiText(str, isLTR); } if (startLevel == -1) { @@ -39283,7 +42322,7 @@ var bidi = PDFJS.bidi = (function bidiClosure() { } var levels = []; - for (var i = 0; i < strLength; ++i) { + for (i = 0; i < strLength; ++i) { levels[i] = startLevel; } @@ -39300,7 +42339,7 @@ var bidi = PDFJS.bidi = (function bidiClosure() { start of the level run, it will get the type of sor. */ var lastType = sor; - for (var i = 0; i < strLength; ++i) { + for (i = 0; i < strLength; ++i) { if (types[i] == 'NSM') { types[i] = lastType; } else { @@ -39313,9 +42352,10 @@ var bidi = PDFJS.bidi = (function bidiClosure() { first strong type (R, L, AL, or sor) is found. If an AL is found, change the type of the European number to Arabic number. */ - var lastType = sor; - for (var i = 0; i < strLength; ++i) { - var t = types[i]; + lastType = sor; + var t; + for (i = 0; i < strLength; ++i) { + t = types[i]; if (t == 'EN') { types[i] = (lastType == 'AL') ? 'AN' : 'EN'; } else if (t == 'R' || t == 'L' || t == 'AL') { @@ -39326,8 +42366,8 @@ var bidi = PDFJS.bidi = (function bidiClosure() { /* W3. Change all ALs to R. */ - for (var i = 0; i < strLength; ++i) { - var t = types[i]; + for (i = 0; i < strLength; ++i) { + t = types[i]; if (t == 'AL') { types[i] = 'R'; } @@ -39338,7 +42378,7 @@ var bidi = PDFJS.bidi = (function bidiClosure() { European number. A single common separator between two numbers of the same type changes to that type: */ - for (var i = 1; i < strLength - 1; ++i) { + for (i = 1; i < strLength - 1; ++i) { if (types[i] == 'ES' && types[i - 1] == 'EN' && types[i + 1] == 'EN') { types[i] = 'EN'; } @@ -39352,17 +42392,18 @@ var bidi = PDFJS.bidi = (function bidiClosure() { W5. A sequence of European terminators adjacent to European numbers changes to all European numbers: */ - for (var i = 0; i < strLength; ++i) { + for (i = 0; i < strLength; ++i) { if (types[i] == 'EN') { // do before - for (var j = i - 1; j >= 0; --j) { + var j; + for (j = i - 1; j >= 0; --j) { if (types[j] != 'ET') { break; } types[j] = 'EN'; } // do after - for (var j = i + 1; j < strLength; --j) { + for (j = i + 1; j < strLength; --j) { if (types[j] != 'ET') { break; } @@ -39374,8 +42415,8 @@ var bidi = PDFJS.bidi = (function bidiClosure() { /* W6. Otherwise, separators and terminators change to Other Neutral: */ - for (var i = 0; i < strLength; ++i) { - var t = types[i]; + for (i = 0; i < strLength; ++i) { + t = types[i]; if (t == 'WS' || t == 'ES' || t == 'ET' || t == 'CS') { types[i] = 'ON'; } @@ -39386,9 +42427,9 @@ var bidi = PDFJS.bidi = (function bidiClosure() { first strong type (R, L, or sor) is found. If an L is found, then change the type of the European number to L. */ - var lastType = sor; - for (var i = 0; i < strLength; ++i) { - var t = types[i]; + lastType = sor; + for (i = 0; i < strLength; ++i) { + t = types[i]; if (t == 'EN') { types[i] = ((lastType == 'L') ? 'L' : 'EN'); } else if (t == 'R' || t == 'L') { @@ -39402,7 +42443,7 @@ var bidi = PDFJS.bidi = (function bidiClosure() { numbers are treated as though they were R. Start-of-level-run (sor) and end-of-level-run (eor) are used at level run boundaries. */ - for (var i = 0; i < strLength; ++i) { + for (i = 0; i < strLength; ++i) { if (types[i] == 'ON') { var end = findUnequal(types, i + 1, 'ON'); var before = sor; @@ -39430,7 +42471,7 @@ var bidi = PDFJS.bidi = (function bidiClosure() { /* N2. Any remaining neutrals take the embedding direction. */ - for (var i = 0; i < strLength; ++i) { + for (i = 0; i < strLength; ++i) { if (types[i] == 'ON') { types[i] = e; } @@ -39443,8 +42484,8 @@ var bidi = PDFJS.bidi = (function bidiClosure() { I2. For all characters with an odd (right-to-left) embedding direction, those of type L, EN or AN go up one level. */ - for (var i = 0; i < strLength; ++i) { - var t = types[i]; + for (i = 0; i < strLength; ++i) { + t = types[i]; if (isEven(levels[i])) { if (t == 'R') { levels[i] += 1; @@ -39480,8 +42521,9 @@ var bidi = PDFJS.bidi = (function bidiClosure() { // find highest level & lowest odd level var highestLevel = -1; var lowestOddLevel = 99; - for (var i = 0, ii = levels.length; i < ii; ++i) { - var level = levels[i]; + var level; + for (i = 0, ii = levels.length; i < ii; ++i) { + level = levels[i]; if (highestLevel < level) { highestLevel = level; } @@ -39491,10 +42533,10 @@ var bidi = PDFJS.bidi = (function bidiClosure() { } // now reverse between those limits - for (var level = highestLevel; level >= lowestOddLevel; --level) { + for (level = highestLevel; level >= lowestOddLevel; --level) { // find segments to reverse var start = -1; - for (var i = 0, ii = levels.length; i < ii; ++i) { + for (i = 0, ii = levels.length; i < ii; ++i) { if (levels[i] < level) { if (start >= 0) { reverseValues(chars, start, i); @@ -39528,1439 +42570,182 @@ var bidi = PDFJS.bidi = (function bidiClosure() { // Finally, return string var result = ''; - for (var i = 0, ii = chars.length; i < ii; ++i) { + for (i = 0, ii = chars.length; i < ii; ++i) { var ch = chars[i]; if (ch != '<' && ch != '>') { result += ch; } } - return new BidiResult(result, isLTR); + return createBidiText(result, isLTR); } return bidi; })(); - -var BUILT_IN_CMAPS = [ -// << Start unicode maps. -'Adobe-GB1-UCS2', -'Adobe-CNS1-UCS2', -'Adobe-Japan1-UCS2', -'Adobe-Korea1-UCS2', -// >> End unicode maps. -'78-EUC-H', -'78-EUC-V', -'78-H', -'78-RKSJ-H', -'78-RKSJ-V', -'78-V', -'78ms-RKSJ-H', -'78ms-RKSJ-V', -'83pv-RKSJ-H', -'90ms-RKSJ-H', -'90ms-RKSJ-V', -'90msp-RKSJ-H', -'90msp-RKSJ-V', -'90pv-RKSJ-H', -'90pv-RKSJ-V', -'Add-H', -'Add-RKSJ-H', -'Add-RKSJ-V', -'Add-V', -'Adobe-CNS1-0', -'Adobe-CNS1-1', -'Adobe-CNS1-2', -'Adobe-CNS1-3', -'Adobe-CNS1-4', -'Adobe-CNS1-5', -'Adobe-CNS1-6', -'Adobe-GB1-0', -'Adobe-GB1-1', -'Adobe-GB1-2', -'Adobe-GB1-3', -'Adobe-GB1-4', -'Adobe-GB1-5', -'Adobe-Japan1-0', -'Adobe-Japan1-1', -'Adobe-Japan1-2', -'Adobe-Japan1-3', -'Adobe-Japan1-4', -'Adobe-Japan1-5', -'Adobe-Japan1-6', -'Adobe-Korea1-0', -'Adobe-Korea1-1', -'Adobe-Korea1-2', -'B5-H', -'B5-V', -'B5pc-H', -'B5pc-V', -'CNS-EUC-H', -'CNS-EUC-V', -'CNS1-H', -'CNS1-V', -'CNS2-H', -'CNS2-V', -'ETHK-B5-H', -'ETHK-B5-V', -'ETen-B5-H', -'ETen-B5-V', -'ETenms-B5-H', -'ETenms-B5-V', -'EUC-H', -'EUC-V', -'Ext-H', -'Ext-RKSJ-H', -'Ext-RKSJ-V', -'Ext-V', -'GB-EUC-H', -'GB-EUC-V', -'GB-H', -'GB-V', -'GBK-EUC-H', -'GBK-EUC-V', -'GBK2K-H', -'GBK2K-V', -'GBKp-EUC-H', -'GBKp-EUC-V', -'GBT-EUC-H', -'GBT-EUC-V', -'GBT-H', -'GBT-V', -'GBTpc-EUC-H', -'GBTpc-EUC-V', -'GBpc-EUC-H', -'GBpc-EUC-V', -'H', -'HKdla-B5-H', -'HKdla-B5-V', -'HKdlb-B5-H', -'HKdlb-B5-V', -'HKgccs-B5-H', -'HKgccs-B5-V', -'HKm314-B5-H', -'HKm314-B5-V', -'HKm471-B5-H', -'HKm471-B5-V', -'HKscs-B5-H', -'HKscs-B5-V', -'Hankaku', -'Hiragana', -'KSC-EUC-H', -'KSC-EUC-V', -'KSC-H', -'KSC-Johab-H', -'KSC-Johab-V', -'KSC-V', -'KSCms-UHC-H', -'KSCms-UHC-HW-H', -'KSCms-UHC-HW-V', -'KSCms-UHC-V', -'KSCpc-EUC-H', -'KSCpc-EUC-V', -'Katakana', -'NWP-H', -'NWP-V', -'RKSJ-H', -'RKSJ-V', -'Roman', -'UniCNS-UCS2-H', -'UniCNS-UCS2-V', -'UniCNS-UTF16-H', -'UniCNS-UTF16-V', -'UniCNS-UTF32-H', -'UniCNS-UTF32-V', -'UniCNS-UTF8-H', -'UniCNS-UTF8-V', -'UniGB-UCS2-H', -'UniGB-UCS2-V', -'UniGB-UTF16-H', -'UniGB-UTF16-V', -'UniGB-UTF32-H', -'UniGB-UTF32-V', -'UniGB-UTF8-H', -'UniGB-UTF8-V', -'UniJIS-UCS2-H', -'UniJIS-UCS2-HW-H', -'UniJIS-UCS2-HW-V', -'UniJIS-UCS2-V', -'UniJIS-UTF16-H', -'UniJIS-UTF16-V', -'UniJIS-UTF32-H', -'UniJIS-UTF32-V', -'UniJIS-UTF8-H', -'UniJIS-UTF8-V', -'UniJIS2004-UTF16-H', -'UniJIS2004-UTF16-V', -'UniJIS2004-UTF32-H', -'UniJIS2004-UTF32-V', -'UniJIS2004-UTF8-H', -'UniJIS2004-UTF8-V', -'UniJISPro-UCS2-HW-V', -'UniJISPro-UCS2-V', -'UniJISPro-UTF8-V', -'UniJISX0213-UTF32-H', -'UniJISX0213-UTF32-V', -'UniJISX02132004-UTF32-H', -'UniJISX02132004-UTF32-V', -'UniKS-UCS2-H', -'UniKS-UCS2-V', -'UniKS-UTF16-H', -'UniKS-UTF16-V', -'UniKS-UTF32-H', -'UniKS-UTF32-V', -'UniKS-UTF8-H', -'UniKS-UTF8-V', -'V', -'WP-Symbol']; - -// CMap, not to be confused with TrueType's cmap. -var CMap = (function CMapClosure() { - function CMap(builtInCMap) { - // Codespace ranges are stored as follows: - // [[1BytePairs], [2BytePairs], [3BytePairs], [4BytePairs]] - // where nBytePairs are ranges e.g. [low1, high1, low2, high2, ...] - this.codespaceRanges = [[], [], [], []]; - this.numCodespaceRanges = 0; - this.map = []; - this.vertical = false; - this.useCMap = null; - this.builtInCMap = builtInCMap; - } - CMap.prototype = { - addCodespaceRange: function(n, low, high) { - this.codespaceRanges[n - 1].push(low, high); - this.numCodespaceRanges++; - }, - - mapRange: function(low, high, dstLow) { - var lastByte = dstLow.length - 1; - while (low <= high) { - this.map[low] = dstLow; - // Only the last byte has to be incremented. - dstLow = dstLow.substr(0, lastByte) + - String.fromCharCode(dstLow.charCodeAt(lastByte) + 1); - ++low; - } - }, - - mapRangeToArray: function(low, high, array) { - var i = 0; - while (low <= high) { - this.map[low] = array[i++]; - ++low; - } - }, - - mapOne: function(src, dst) { - this.map[src] = dst; - }, - - lookup: function(code) { - return this.map[code]; - }, - - readCharCode: function(str, offset) { - var c = 0; - var codespaceRanges = this.codespaceRanges; - var codespaceRangesLen = this.codespaceRanges.length; - // 9.7.6.2 CMap Mapping - // The code length is at most 4. - for (var n = 0; n < codespaceRangesLen; n++) { - c = ((c << 8) | str.charCodeAt(offset + n)) >>> 0; - // Check each codespace range to see if it falls within. - var codespaceRange = codespaceRanges[n]; - for (var k = 0, kk = codespaceRange.length; k < kk;) { - var low = codespaceRange[k++]; - var high = codespaceRange[k++]; - if (c >= low && c <= high) { - return [c, n + 1]; - } - } - } - - return [0, 1]; - } - - }; - return CMap; -})(); - -var IdentityCMap = (function IdentityCMapClosure() { - function IdentityCMap(vertical, n) { - CMap.call(this); - this.vertical = vertical; - this.addCodespaceRange(n, 0, 0xffff); - this.mapRange(0, 0xffff, '\u0000'); - } - Util.inherit(IdentityCMap, CMap, {}); - - return IdentityCMap; -})(); - -var CMapFactory = (function CMapFactoryClosure() { - function strToInt(str) { - var a = 0; - for (var i = 0; i < str.length; i++) { - a = (a << 8) | str.charCodeAt(i); - } - return a >>> 0; - } - - function expectString(obj) { - if (!isString(obj)) { - error('Malformed CMap: expected string.'); - } - } - - function expectInt(obj) { - if (!isInt(obj)) { - error('Malformed CMap: expected int.'); - } - } - - function parseBfChar(cMap, lexer) { - while (true) { - var obj = lexer.getObj(); - if (isEOF(obj)) { - break; - } - if (isCmd(obj, 'endbfchar')) { - return; - } - expectString(obj); - var src = strToInt(obj); - obj = lexer.getObj(); - // TODO are /dstName used? - expectString(obj); - var dst = obj; - cMap.mapOne(src, dst); - } - } - - function parseBfRange(cMap, lexer) { - while (true) { - var obj = lexer.getObj(); - if (isEOF(obj)) { - break; - } - if (isCmd(obj, 'endbfrange')) { - return; - } - expectString(obj); - var low = strToInt(obj); - obj = lexer.getObj(); - expectString(obj); - var high = strToInt(obj); - obj = lexer.getObj(); - if (isInt(obj) || isString(obj)) { - var dstLow = isInt(obj) ? String.fromCharCode(obj) : obj; - cMap.mapRange(low, high, dstLow); - } else if (isCmd(obj, '[')) { - obj = lexer.getObj(); - var array = []; - while (!isCmd(obj, ']') && !isEOF(obj)) { - array.push(obj); - obj = lexer.getObj(); - } - cMap.mapRangeToArray(low, high, array); - } else { - break; - } - } - error('Invalid bf range.'); - } - - function parseCidChar(cMap, lexer) { - while (true) { - var obj = lexer.getObj(); - if (isEOF(obj)) { - break; - } - if (isCmd(obj, 'endcidchar')) { - return; - } - expectString(obj); - var src = strToInt(obj); - obj = lexer.getObj(); - expectInt(obj); - var dst = String.fromCharCode(obj); - cMap.mapOne(src, dst); - } - } - - function parseCidRange(cMap, lexer) { - while (true) { - var obj = lexer.getObj(); - if (isEOF(obj)) { - break; - } - if (isCmd(obj, 'endcidrange')) { - return; - } - expectString(obj); - var low = strToInt(obj); - obj = lexer.getObj(); - expectString(obj); - var high = strToInt(obj); - obj = lexer.getObj(); - expectInt(obj); - var dstLow = String.fromCharCode(obj); - cMap.mapRange(low, high, dstLow); - } - } - - function parseCodespaceRange(cMap, lexer) { - while (true) { - var obj = lexer.getObj(); - if (isEOF(obj)) { - break; - } - if (isCmd(obj, 'endcodespacerange')) { - return; - } - if (!isString(obj)) { - break; - } - var low = strToInt(obj); - obj = lexer.getObj(); - if (!isString(obj)) { - break; - } - var high = strToInt(obj); - cMap.addCodespaceRange(obj.length, low, high); - } - error('Invalid codespace range.'); - } - - function parseWMode(cMap, lexer) { - var obj = lexer.getObj(); - if (isInt(obj)) { - cMap.vertical = !!obj; - } - } - - function parseCMap(cMap, lexer, builtInCMapUrl, useCMap) { - var previous; - var embededUseCMap; - objLoop: while (true) { - var obj = lexer.getObj(); - if (isEOF(obj)) { - break; - } else if (isName(obj)) { - if (obj.name === 'WMode') { - parseWMode(cMap, lexer); - } - previous = obj; - } else if (isCmd(obj)) { - switch (obj.cmd) { - case 'endcmap': - break objLoop; - case 'usecmap': - if (isName(previous)) { - embededUseCMap = previous.name; - } - break; - case 'begincodespacerange': - parseCodespaceRange(cMap, lexer); - break; - case 'beginbfchar': - parseBfChar(cMap, lexer); - break; - case 'begincidchar': - parseCidChar(cMap, lexer); - break; - case 'beginbfrange': - parseBfRange(cMap, lexer); - break; - case 'begincidrange': - parseCidRange(cMap, lexer); - break; - } - } - } - - if (!useCMap && embededUseCMap) { - // Load the usecmap definition from the file only if there wasn't one - // specified. - useCMap = embededUseCMap; - } - if (useCMap) { - cMap.useCMap = createBuiltInCMap(useCMap, builtInCMapUrl); - // If there aren't any code space ranges defined clone all the parent ones - // into this cMap. - if (cMap.numCodespaceRanges === 0) { - var useCodespaceRanges = cMap.useCMap.codespaceRanges; - for (var i = 0; i < useCodespaceRanges.length; i++) { - cMap.codespaceRanges[i] = useCodespaceRanges[i].slice(); - } - cMap.numCodespaceRanges = cMap.useCMap.numCodespaceRanges; - } - // Merge the map into the current one, making sure not to override - // any previously defined entries. - for (var key in cMap.useCMap.map) { - if (key in cMap.map) { - continue; - } - cMap.map[key] = cMap.useCMap.map[key]; - } - } - } - - function createBuiltInCMap(name, builtInCMapUrl) { - if (name === 'Identity-H') { - return new IdentityCMap(false, 2); - } else if (name === 'Identity-V') { - return new IdentityCMap(true, 2); - } - if (BUILT_IN_CMAPS.indexOf(name) === -1) { - error('Unknown cMap name: ' + name); - } - - var request = new XMLHttpRequest(); - var url = builtInCMapUrl + name; - request.open('GET', url, false); - request.send(null); - if (request.status === 0 && /^https?:/i.test(url)) { - error('Unable to get cMap at: ' + url); - } - var cMap = new CMap(true); - var lexer = new Lexer(new StringStream(request.responseText)); - parseCMap(cMap, lexer, builtInCMapUrl, null); - return cMap; - } - - return { - create: function (encoding, builtInCMapUrl, useCMap) { - if (isName(encoding)) { - return createBuiltInCMap(encoding.name, builtInCMapUrl); - } else if (isStream(encoding)) { - var cMap = new CMap(); - var lexer = new Lexer(encoding); - try { - parseCMap(cMap, lexer, builtInCMapUrl, useCMap); - } catch (e) { - warn('Invalid CMap data. ' + e); - } - return cMap; - } - error('Encoding required.'); - } - }; -})(); -/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ -/* - Copyright 2011 notmasteryet - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -// - The JPEG specification can be found in the ITU CCITT Recommendation T.81 -// (www.w3.org/Graphics/JPEG/itu-t81.pdf) -// - The JFIF specification can be found in the JPEG File Interchange Format -// (www.w3.org/Graphics/JPEG/jfif3.pdf) -// - The Adobe Application-Specific JPEG markers in the Supporting the DCT Filters -// in PostScript Level 2, Technical Note #5116 -// (partners.adobe.com/public/developer/en/ps/sdk/5116.DCT_Filter.pdf) +/* Copyright 2014 Opera Software ASA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * Based on https://code.google.com/p/smhasher/wiki/MurmurHash3. + * Hashes roughly 100 KB per millisecond on i7 3.4 GHz. + */ +/* globals Uint32ArrayView */ -var JpegImage = (function jpegImage() { - "use strict"; - var dctZigZag = new Int32Array([ - 0, - 1, 8, - 16, 9, 2, - 3, 10, 17, 24, - 32, 25, 18, 11, 4, - 5, 12, 19, 26, 33, 40, - 48, 41, 34, 27, 20, 13, 6, - 7, 14, 21, 28, 35, 42, 49, 56, - 57, 50, 43, 36, 29, 22, 15, - 23, 30, 37, 44, 51, 58, - 59, 52, 45, 38, 31, - 39, 46, 53, 60, - 61, 54, 47, - 55, 62, - 63 - ]); +'use strict'; - var dctCos1 = 4017 // cos(pi/16) - var dctSin1 = 799 // sin(pi/16) - var dctCos3 = 3406 // cos(3*pi/16) - var dctSin3 = 2276 // sin(3*pi/16) - var dctCos6 = 1567 // cos(6*pi/16) - var dctSin6 = 3784 // sin(6*pi/16) - var dctSqrt2 = 5793 // sqrt(2) - var dctSqrt1d2 = 2896 // sqrt(2) / 2 +var MurmurHash3_64 = (function MurmurHash3_64Closure (seed) { + // Workaround for missing math precison in JS. + var MASK_HIGH = 0xffff0000; + var MASK_LOW = 0xffff; - function constructor() { + function MurmurHash3_64 (seed) { + var SEED = 0xc3d2e1f0; + this.h1 = seed ? seed & 0xffffffff : SEED; + this.h2 = seed ? seed & 0xffffffff : SEED; } - function buildHuffmanTable(codeLengths, values) { - var k = 0, code = [], i, j, length = 16; - while (length > 0 && !codeLengths[length - 1]) - length--; - code.push({children: [], index: 0}); - var p = code[0], q; - for (i = 0; i < length; i++) { - for (j = 0; j < codeLengths[i]; j++) { - p = code.pop(); - p.children[p.index] = values[k]; - while (p.index > 0) { - p = code.pop(); - } - p.index++; - code.push(p); - while (code.length <= i) { - code.push(q = {children: [], index: 0}); - p.children[p.index] = q.children; - p = q; - } - k++; - } - if (i + 1 < length) { - // p here points to last code - code.push(q = {children: [], index: 0}); - p.children[p.index] = q.children; - p = q; - } - } - return code[0].children; + var alwaysUseUint32ArrayView = false; + // old webkits have issues with non-aligned arrays + try { + new Uint32Array(new Uint8Array(5).buffer, 0, 1); + } catch (e) { + alwaysUseUint32ArrayView = true; } - function decodeScan(data, offset, - frame, components, resetInterval, - spectralStart, spectralEnd, - successivePrev, successive) { - var precision = frame.precision; - var samplesPerLine = frame.samplesPerLine; - var scanLines = frame.scanLines; - var mcusPerLine = frame.mcusPerLine; - var progressive = frame.progressive; - var maxH = frame.maxH, maxV = frame.maxV; - - var startOffset = offset, bitsData = 0, bitsCount = 0; - function readBit() { - if (bitsCount > 0) { - bitsCount--; - return (bitsData >> bitsCount) & 1; - } - bitsData = data[offset++]; - if (bitsData == 0xFF) { - var nextByte = data[offset++]; - if (nextByte) { - throw "unexpected marker: " + ((bitsData << 8) | nextByte).toString(16); - } - // unstuff 0 - } - bitsCount = 7; - return bitsData >>> 7; - } - function decodeHuffman(tree) { - var node = tree, bit; - while ((bit = readBit()) !== null) { - node = node[bit]; - if (typeof node === 'number') - return node; - if (typeof node !== 'object') - throw "invalid huffman sequence"; - } - return null; - } - function receive(length) { - var n = 0; - while (length > 0) { - var bit = readBit(); - if (bit === null) return; - n = (n << 1) | bit; - length--; - } - return n; - } - function receiveAndExtend(length) { - var n = receive(length); - if (n >= 1 << (length - 1)) - return n; - return n + (-1 << length) + 1; - } - function decodeBaseline(component, zz) { - var t = decodeHuffman(component.huffmanTableDC); - var diff = t === 0 ? 0 : receiveAndExtend(t); - zz[0]= (component.pred += diff); - var k = 1; - while (k < 64) { - var rs = decodeHuffman(component.huffmanTableAC); - var s = rs & 15, r = rs >> 4; - if (s === 0) { - if (r < 15) - break; - k += 16; - continue; - } - k += r; - var z = dctZigZag[k]; - zz[z] = receiveAndExtend(s); - k++; - } - } - function decodeDCFirst(component, zz) { - var t = decodeHuffman(component.huffmanTableDC); - var diff = t === 0 ? 0 : (receiveAndExtend(t) << successive); - zz[0] = (component.pred += diff); - } - function decodeDCSuccessive(component, zz) { - zz[0] |= readBit() << successive; - } - var eobrun = 0; - function decodeACFirst(component, zz) { - if (eobrun > 0) { - eobrun--; - return; - } - var k = spectralStart, e = spectralEnd; - while (k <= e) { - var rs = decodeHuffman(component.huffmanTableAC); - var s = rs & 15, r = rs >> 4; - if (s === 0) { - if (r < 15) { - eobrun = receive(r) + (1 << r) - 1; - break; - } - k += 16; - continue; - } - k += r; - var z = dctZigZag[k]; - zz[z] = receiveAndExtend(s) * (1 << successive); - k++; - } - } - var successiveACState = 0, successiveACNextValue; - function decodeACSuccessive(component, zz) { - var k = spectralStart, e = spectralEnd, r = 0; - while (k <= e) { - var z = dctZigZag[k]; - switch (successiveACState) { - case 0: // initial state - var rs = decodeHuffman(component.huffmanTableAC); - var s = rs & 15, r = rs >> 4; - if (s === 0) { - if (r < 15) { - eobrun = receive(r) + (1 << r); - successiveACState = 4; - } else { - r = 16; - successiveACState = 1; - } - } else { - if (s !== 1) - throw "invalid ACn encoding"; - successiveACNextValue = receiveAndExtend(s); - successiveACState = r ? 2 : 3; - } - continue; - case 1: // skipping r zero items - case 2: - if (zz[z]) - zz[z] += (readBit() << successive); - else { - r--; - if (r === 0) - successiveACState = successiveACState == 2 ? 3 : 0; + MurmurHash3_64.prototype = { + update: function MurmurHash3_64_update(input) { + var useUint32ArrayView = alwaysUseUint32ArrayView; + var i; + if (typeof input == 'string') { + var data = new Uint8Array(input.length * 2); + var length = 0; + for (i = 0; i < input.length; i++) { + var code = input.charCodeAt(i); + if (code <= 0xff) { + data[length++] = code; } - break; - case 3: // set value for a zero item - if (zz[z]) - zz[z] += (readBit() << successive); else { - zz[z] = successiveACNextValue << successive; - successiveACState = 0; + data[length++] = code >>> 8; + data[length++] = code & 0xff; } - break; - case 4: // eob - if (zz[z]) - zz[z] += (readBit() << successive); - break; - } - k++; - } - if (successiveACState === 4) { - eobrun--; - if (eobrun === 0) - successiveACState = 0; - } - } - function decodeMcu(component, decode, mcu, row, col) { - var mcuRow = (mcu / mcusPerLine) | 0; - var mcuCol = mcu % mcusPerLine; - var blockRow = mcuRow * component.v + row; - var blockCol = mcuCol * component.h + col; - decode(component, component.blocks[blockRow][blockCol]); - } - function decodeBlock(component, decode, mcu) { - var blockRow = (mcu / component.blocksPerLine) | 0; - var blockCol = mcu % component.blocksPerLine; - decode(component, component.blocks[blockRow][blockCol]); - } - - var componentsLength = components.length; - var component, i, j, k, n; - var decodeFn; - if (progressive) { - if (spectralStart === 0) - decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive; - else - decodeFn = successivePrev === 0 ? decodeACFirst : decodeACSuccessive; - } else { - decodeFn = decodeBaseline; - } - - var mcu = 0, marker; - var mcuExpected; - if (componentsLength == 1) { - mcuExpected = components[0].blocksPerLine * components[0].blocksPerColumn; - } else { - mcuExpected = mcusPerLine * frame.mcusPerColumn; - } - if (!resetInterval) resetInterval = mcuExpected; - - var h, v; - while (mcu < mcuExpected) { - // reset interval stuff - for (i = 0; i < componentsLength; i++) - components[i].pred = 0; - eobrun = 0; - - if (componentsLength == 1) { - component = components[0]; - for (n = 0; n < resetInterval; n++) { - decodeBlock(component, decodeFn, mcu); - mcu++; } + } else if (input instanceof Uint8Array) { + data = input; + length = data.length; + } else if (typeof input === 'object' && ('length' in input)) { + // processing regular arrays as well, e.g. for IE9 + data = input; + length = data.length; + useUint32ArrayView = true; } else { - for (n = 0; n < resetInterval; n++) { - for (i = 0; i < componentsLength; i++) { - component = components[i]; - h = component.h; - v = component.v; - for (j = 0; j < v; j++) { - for (k = 0; k < h; k++) { - decodeMcu(component, decodeFn, mcu, j, k); - } - } - } - mcu++; - } - } - - // find marker - bitsCount = 0; - marker = (data[offset] << 8) | data[offset + 1]; - if (marker <= 0xFF00) { - throw "marker was not found"; - } - - if (marker >= 0xFFD0 && marker <= 0xFFD7) { // RSTx - offset += 2; - } - else - break; - } - - return offset - startOffset; - } - - function buildComponentData(frame, component) { - var lines = []; - var blocksPerLine = component.blocksPerLine; - var blocksPerColumn = component.blocksPerColumn; - var samplesPerLine = blocksPerLine << 3; - var R = new Int32Array(64); - - // A port of poppler's IDCT method which in turn is taken from: - // Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz, - // "Practical Fast 1-D DCT Algorithms with 11 Multiplications", - // IEEE Intl. Conf. on Acoustics, Speech & Signal Processing, 1989, - // 988-991. - function quantizeAndInverse(zz, p) { - var qt = component.quantizationTable; - var v0, v1, v2, v3, v4, v5, v6, v7, t; - var i; - - // dequant - for (i = 0; i < 64; i++) - p[i] = zz[i] * qt[i]; - - // inverse DCT on rows - for (i = 0; i < 8; ++i) { - var row = 8 * i; - - // check for all-zero AC coefficients - if (p[1 + row] == 0 && p[2 + row] == 0 && p[3 + row] == 0 && - p[4 + row] == 0 && p[5 + row] == 0 && p[6 + row] == 0 && - p[7 + row] == 0) { - t = (dctSqrt2 * p[0 + row] + 512) >> 10; - p[0 + row] = t; - p[1 + row] = t; - p[2 + row] = t; - p[3 + row] = t; - p[4 + row] = t; - p[5 + row] = t; - p[6 + row] = t; - p[7 + row] = t; - continue; - } - - // stage 4 - v0 = (dctSqrt2 * p[0 + row] + 128) >> 8; - v1 = (dctSqrt2 * p[4 + row] + 128) >> 8; - v2 = p[2 + row]; - v3 = p[6 + row]; - v4 = (dctSqrt1d2 * (p[1 + row] - p[7 + row]) + 128) >> 8; - v7 = (dctSqrt1d2 * (p[1 + row] + p[7 + row]) + 128) >> 8; - v5 = p[3 + row] << 4; - v6 = p[5 + row] << 4; - - // stage 3 - t = (v0 - v1+ 1) >> 1; - v0 = (v0 + v1 + 1) >> 1; - v1 = t; - t = (v2 * dctSin6 + v3 * dctCos6 + 128) >> 8; - v2 = (v2 * dctCos6 - v3 * dctSin6 + 128) >> 8; - v3 = t; - t = (v4 - v6 + 1) >> 1; - v4 = (v4 + v6 + 1) >> 1; - v6 = t; - t = (v7 + v5 + 1) >> 1; - v5 = (v7 - v5 + 1) >> 1; - v7 = t; - - // stage 2 - t = (v0 - v3 + 1) >> 1; - v0 = (v0 + v3 + 1) >> 1; - v3 = t; - t = (v1 - v2 + 1) >> 1; - v1 = (v1 + v2 + 1) >> 1; - v2 = t; - t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12; - v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12; - v7 = t; - t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12; - v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12; - v6 = t; - - // stage 1 - p[0 + row] = v0 + v7; - p[7 + row] = v0 - v7; - p[1 + row] = v1 + v6; - p[6 + row] = v1 - v6; - p[2 + row] = v2 + v5; - p[5 + row] = v2 - v5; - p[3 + row] = v3 + v4; - p[4 + row] = v3 - v4; + throw new Error('Wrong data format in MurmurHash3_64_update. ' + + 'Input must be a string or array.'); } - // inverse DCT on columns - for (i = 0; i < 8; ++i) { - var col = i; + var blockCounts = length >> 2; + var tailLength = length - blockCounts * 4; + // we don't care about endianness here + var dataUint32 = useUint32ArrayView ? + new Uint32ArrayView(data, blockCounts) : + new Uint32Array(data.buffer, 0, blockCounts); + var k1 = 0; + var k2 = 0; + var h1 = this.h1; + var h2 = this.h2; + var C1 = 0xcc9e2d51; + var C2 = 0x1b873593; + var C1_LOW = C1 & MASK_LOW; + var C2_LOW = C2 & MASK_LOW; - // check for all-zero AC coefficients - if (p[1*8 + col] == 0 && p[2*8 + col] == 0 && p[3*8 + col] == 0 && - p[4*8 + col] == 0 && p[5*8 + col] == 0 && p[6*8 + col] == 0 && - p[7*8 + col] == 0) { - t = (dctSqrt2 * p[i+0] + 8192) >> 14; - p[0*8 + col] = t; - p[1*8 + col] = t; - p[2*8 + col] = t; - p[3*8 + col] = t; - p[4*8 + col] = t; - p[5*8 + col] = t; - p[6*8 + col] = t; - p[7*8 + col] = t; - continue; + for (i = 0; i < blockCounts; i++) { + if (i & 1) { + k1 = dataUint32[i]; + k1 = (k1 * C1 & MASK_HIGH) | (k1 * C1_LOW & MASK_LOW); + k1 = k1 << 15 | k1 >>> 17; + k1 = (k1 * C2 & MASK_HIGH) | (k1 * C2_LOW & MASK_LOW); + h1 ^= k1; + h1 = h1 << 13 | h1 >>> 19; + h1 = h1 * 5 + 0xe6546b64; + } else { + k2 = dataUint32[i]; + k2 = (k2 * C1 & MASK_HIGH) | (k2 * C1_LOW & MASK_LOW); + k2 = k2 << 15 | k2 >>> 17; + k2 = (k2 * C2 & MASK_HIGH) | (k2 * C2_LOW & MASK_LOW); + h2 ^= k2; + h2 = h2 << 13 | h2 >>> 19; + h2 = h2 * 5 + 0xe6546b64; } - - // stage 4 - v0 = (dctSqrt2 * p[0*8 + col] + 2048) >> 12; - v1 = (dctSqrt2 * p[4*8 + col] + 2048) >> 12; - v2 = p[2*8 + col]; - v3 = p[6*8 + col]; - v4 = (dctSqrt1d2 * (p[1*8 + col] - p[7*8 + col]) + 2048) >> 12; - v7 = (dctSqrt1d2 * (p[1*8 + col] + p[7*8 + col]) + 2048) >> 12; - v5 = p[3*8 + col]; - v6 = p[5*8 + col]; - - // stage 3 - t = (v0 - v1 + 1) >> 1; - v0 = (v0 + v1 + 1) >> 1; - v1 = t; - t = (v2 * dctSin6 + v3 * dctCos6 + 2048) >> 12; - v2 = (v2 * dctCos6 - v3 * dctSin6 + 2048) >> 12; - v3 = t; - t = (v4 - v6 + 1) >> 1; - v4 = (v4 + v6 + 1) >> 1; - v6 = t; - t = (v7 + v5 + 1) >> 1; - v5 = (v7 - v5 + 1) >> 1; - v7 = t; - - // stage 2 - t = (v0 - v3 + 1) >> 1; - v0 = (v0 + v3 + 1) >> 1; - v3 = t; - t = (v1 - v2 + 1) >> 1; - v1 = (v1 + v2 + 1) >> 1; - v2 = t; - t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12; - v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12; - v7 = t; - t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12; - v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12; - v6 = t; - - // stage 1 - p[0*8 + col] = v0 + v7; - p[7*8 + col] = v0 - v7; - p[1*8 + col] = v1 + v6; - p[6*8 + col] = v1 - v6; - p[2*8 + col] = v2 + v5; - p[5*8 + col] = v2 - v5; - p[3*8 + col] = v3 + v4; - p[4*8 + col] = v3 - v4; } - // convert to 8-bit integers - for (i = 0; i < 64; ++i) { - p[i] = clampTo8bit((p[i] + 2056) >> 4); - } - } - - var i, j; - for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) { - var scanLine = blockRow << 3; - for (i = 0; i < 8; i++) - lines.push(new Uint8Array(samplesPerLine)); - for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) { - quantizeAndInverse(component.blocks[blockRow][blockCol], R); + k1 = 0; - var offset = 0, sample = blockCol << 3; - for (j = 0; j < 8; j++) { - var line = lines[scanLine + j]; - for (i = 0; i < 8; i++) - line[sample + i] = R[offset++]; + switch (tailLength) { + case 3: + k1 ^= data[blockCounts * 4 + 2] << 16; + /* falls through */ + case 2: + k1 ^= data[blockCounts * 4 + 1] << 8; + /* falls through */ + case 1: + k1 ^= data[blockCounts * 4]; + /* falls through */ + k1 = (k1 * C1 & MASK_HIGH) | (k1 * C1_LOW & MASK_LOW); + k1 = k1 << 15 | k1 >>> 17; + k1 = (k1 * C2 & MASK_HIGH) | (k1 * C2_LOW & MASK_LOW); + if (blockCounts & 1) { + h1 ^= k1; + } else { + h2 ^= k1; } } - } - return lines; - } - - function clampTo8bit(a) { - return a < 0 ? 0 : a > 255 ? 255 : a; - } - constructor.prototype = { - load: function load(path) { - var xhr = new XMLHttpRequest(); - xhr.open("GET", path, true); - xhr.responseType = "arraybuffer"; - xhr.onload = (function() { - // TODO catch parse error - var data = new Uint8Array(xhr.response || xhr.mozResponseArrayBuffer); - this.parse(data); - if (this.onload) - this.onload(); - }).bind(this); - xhr.send(null); + this.h1 = h1; + this.h2 = h2; + return this; }, - parse: function parse(data) { - var offset = 0, length = data.length; - function readUint16() { - var value = (data[offset] << 8) | data[offset + 1]; - offset += 2; - return value; - } - function readDataBlock() { - var length = readUint16(); - var array = data.subarray(offset, offset + length - 2); - offset += array.length; - return array; - } - function prepareComponents(frame) { - var maxH = 0, maxV = 0; - var component, componentId; - for (componentId in frame.components) { - if (frame.components.hasOwnProperty(componentId)) { - component = frame.components[componentId]; - if (maxH < component.h) maxH = component.h; - if (maxV < component.v) maxV = component.v; - } - } - var mcusPerLine = Math.ceil(frame.samplesPerLine / 8 / maxH); - var mcusPerColumn = Math.ceil(frame.scanLines / 8 / maxV); - for (componentId in frame.components) { - if (frame.components.hasOwnProperty(componentId)) { - component = frame.components[componentId]; - var blocksPerLine = Math.ceil(Math.ceil(frame.samplesPerLine / 8) * component.h / maxH); - var blocksPerColumn = Math.ceil(Math.ceil(frame.scanLines / 8) * component.v / maxV); - var blocksPerLineForMcu = mcusPerLine * component.h; - var blocksPerColumnForMcu = mcusPerColumn * component.v; - var blocks = []; - for (var i = 0; i < blocksPerColumnForMcu; i++) { - var row = []; - for (var j = 0; j < blocksPerLineForMcu; j++) - row.push(new Int16Array(64)); - blocks.push(row); - } - component.blocksPerLine = blocksPerLine; - component.blocksPerColumn = blocksPerColumn; - component.blocks = blocks; - } - } - frame.maxH = maxH; - frame.maxV = maxV; - frame.mcusPerLine = mcusPerLine; - frame.mcusPerColumn = mcusPerColumn; - } - var jfif = null; - var adobe = null; - var pixels = null; - var frame, resetInterval; - var quantizationTables = [], frames = []; - var huffmanTablesAC = [], huffmanTablesDC = []; - var fileMarker = readUint16(); - if (fileMarker != 0xFFD8) { // SOI (Start of Image) - throw "SOI not found"; - } - - fileMarker = readUint16(); - while (fileMarker != 0xFFD9) { // EOI (End of image) - var i, j, l; - switch(fileMarker) { - case 0xFFE0: // APP0 (Application Specific) - case 0xFFE1: // APP1 - case 0xFFE2: // APP2 - case 0xFFE3: // APP3 - case 0xFFE4: // APP4 - case 0xFFE5: // APP5 - case 0xFFE6: // APP6 - case 0xFFE7: // APP7 - case 0xFFE8: // APP8 - case 0xFFE9: // APP9 - case 0xFFEA: // APP10 - case 0xFFEB: // APP11 - case 0xFFEC: // APP12 - case 0xFFED: // APP13 - case 0xFFEE: // APP14 - case 0xFFEF: // APP15 - case 0xFFFE: // COM (Comment) - var appData = readDataBlock(); - - if (fileMarker === 0xFFE0) { - if (appData[0] === 0x4A && appData[1] === 0x46 && appData[2] === 0x49 && - appData[3] === 0x46 && appData[4] === 0) { // 'JFIF\x00' - jfif = { - version: { major: appData[5], minor: appData[6] }, - densityUnits: appData[7], - xDensity: (appData[8] << 8) | appData[9], - yDensity: (appData[10] << 8) | appData[11], - thumbWidth: appData[12], - thumbHeight: appData[13], - thumbData: appData.subarray(14, 14 + 3 * appData[12] * appData[13]) - }; - } - } - // TODO APP1 - Exif - if (fileMarker === 0xFFEE) { - if (appData[0] === 0x41 && appData[1] === 0x64 && appData[2] === 0x6F && - appData[3] === 0x62 && appData[4] === 0x65 && appData[5] === 0) { // 'Adobe\x00' - adobe = { - version: appData[6], - flags0: (appData[7] << 8) | appData[8], - flags1: (appData[9] << 8) | appData[10], - transformCode: appData[11] - }; - } - } - break; - - case 0xFFDB: // DQT (Define Quantization Tables) - var quantizationTablesLength = readUint16(); - var quantizationTablesEnd = quantizationTablesLength + offset - 2; - while (offset < quantizationTablesEnd) { - var quantizationTableSpec = data[offset++]; - var tableData = new Int32Array(64); - if ((quantizationTableSpec >> 4) === 0) { // 8 bit values - for (j = 0; j < 64; j++) { - var z = dctZigZag[j]; - tableData[z] = data[offset++]; - } - } else if ((quantizationTableSpec >> 4) === 1) { //16 bit - for (j = 0; j < 64; j++) { - var z = dctZigZag[j]; - tableData[z] = readUint16(); - } - } else - throw "DQT: invalid table spec"; - quantizationTables[quantizationTableSpec & 15] = tableData; - } - break; - - case 0xFFC0: // SOF0 (Start of Frame, Baseline DCT) - case 0xFFC1: // SOF1 (Start of Frame, Extended DCT) - case 0xFFC2: // SOF2 (Start of Frame, Progressive DCT) - readUint16(); // skip data length - frame = {}; - frame.extended = (fileMarker === 0xFFC1); - frame.progressive = (fileMarker === 0xFFC2); - frame.precision = data[offset++]; - frame.scanLines = readUint16(); - frame.samplesPerLine = readUint16(); - frame.components = {}; - frame.componentsOrder = []; - var componentsCount = data[offset++], componentId; - var maxH = 0, maxV = 0; - for (i = 0; i < componentsCount; i++) { - componentId = data[offset]; - var h = data[offset + 1] >> 4; - var v = data[offset + 1] & 15; - var qId = data[offset + 2]; - frame.componentsOrder.push(componentId); - frame.components[componentId] = { - h: h, - v: v, - quantizationTable: quantizationTables[qId] - }; - offset += 3; - } - prepareComponents(frame); - frames.push(frame); - break; - - case 0xFFC4: // DHT (Define Huffman Tables) - var huffmanLength = readUint16(); - for (i = 2; i < huffmanLength;) { - var huffmanTableSpec = data[offset++]; - var codeLengths = new Uint8Array(16); - var codeLengthSum = 0; - for (j = 0; j < 16; j++, offset++) - codeLengthSum += (codeLengths[j] = data[offset]); - var huffmanValues = new Uint8Array(codeLengthSum); - for (j = 0; j < codeLengthSum; j++, offset++) - huffmanValues[j] = data[offset]; - i += 17 + codeLengthSum; - ((huffmanTableSpec >> 4) === 0 ? - huffmanTablesDC : huffmanTablesAC)[huffmanTableSpec & 15] = - buildHuffmanTable(codeLengths, huffmanValues); - } - break; + hexdigest: function MurmurHash3_64_hexdigest () { + var h1 = this.h1; + var h2 = this.h2; - case 0xFFDD: // DRI (Define Restart Interval) - readUint16(); // skip data length - resetInterval = readUint16(); - break; + h1 ^= h2 >>> 1; + h1 = (h1 * 0xed558ccd & MASK_HIGH) | (h1 * 0x8ccd & MASK_LOW); + h2 = (h2 * 0xff51afd7 & MASK_HIGH) | + (((h2 << 16 | h1 >>> 16) * 0xafd7ed55 & MASK_HIGH) >>> 16); + h1 ^= h2 >>> 1; + h1 = (h1 * 0x1a85ec53 & MASK_HIGH) | (h1 * 0xec53 & MASK_LOW); + h2 = (h2 * 0xc4ceb9fe & MASK_HIGH) | + (((h2 << 16 | h1 >>> 16) * 0xb9fe1a85 & MASK_HIGH) >>> 16); + h1 ^= h2 >>> 1; - case 0xFFDA: // SOS (Start of Scan) - var scanLength = readUint16(); - var selectorsCount = data[offset++]; - var components = [], component; - for (i = 0; i < selectorsCount; i++) { - component = frame.components[data[offset++]]; - var tableSpec = data[offset++]; - component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4]; - component.huffmanTableAC = huffmanTablesAC[tableSpec & 15]; - components.push(component); - } - var spectralStart = data[offset++]; - var spectralEnd = data[offset++]; - var successiveApproximation = data[offset++]; - var processed = decodeScan(data, offset, - frame, components, resetInterval, - spectralStart, spectralEnd, - successiveApproximation >> 4, successiveApproximation & 15); - offset += processed; - break; - default: - if (data[offset - 3] == 0xFF && - data[offset - 2] >= 0xC0 && data[offset - 2] <= 0xFE) { - // could be incorrect encoding -- last 0xFF byte of the previous - // block was eaten by the encoder - offset -= 3; - break; - } - throw "unknown JPEG marker " + fileMarker.toString(16); + for (var i = 0, arr = [h1, h2], str = ''; i < arr.length; i++) { + var hex = (arr[i] >>> 0).toString(16); + while (hex.length < 8) { + hex = '0' + hex; } - fileMarker = readUint16(); + str += hex; } - if (frames.length != 1) - throw "only single frame JPEGs supported"; - this.width = frame.samplesPerLine; - this.height = frame.scanLines; - this.jfif = jfif; - this.adobe = adobe; - this.components = []; - for (var i = 0; i < frame.componentsOrder.length; i++) { - var component = frame.components[frame.componentsOrder[i]]; - this.components.push({ - lines: buildComponentData(frame, component), - scaleX: component.h / frame.maxH, - scaleY: component.v / frame.maxV - }); - } - }, - getData: function getData(width, height) { - var scaleX = this.width / width, scaleY = this.height / height; - - var component, componentLine, componentScaleX, componentScaleY; - var x, y, i; - var offset = 0; - var Y, Cb, Cr, K, C, M, Ye, R, G, B; - var colorTransform; - var numComponents = this.components.length; - var dataLength = width * height * numComponents; - var data = new Uint8Array(dataLength); - - // First construct image data ... - for (i = 0; i < numComponents; i++) { - component = this.components[i]; - componentScaleX = component.scaleX * scaleX; - componentScaleY = component.scaleY * scaleY; - offset = i; - for (y = 0; y < height; y++) { - componentLine = component.lines[0 | (y * componentScaleY)]; - for (x = 0; x < width; x++) { - data[offset] = componentLine[0 | (x * componentScaleX)]; - offset += numComponents; - } - } - } - - // ... then transform colors, if necessary - switch (numComponents) { - case 1: case 2: break; - // no color conversion for one or two compoenents - - case 3: - // The default transform for three components is true - colorTransform = true; - // The adobe transform marker overrides any previous setting - if (this.adobe && this.adobe.transformCode) - colorTransform = true; - else if (typeof this.colorTransform !== 'undefined') - colorTransform = !!this.colorTransform; - - if (colorTransform) { - for (i = 0; i < dataLength; i += numComponents) { - Y = data[i ]; - Cb = data[i + 1]; - Cr = data[i + 2]; - - R = clampTo8bit(Y + 1.402 * (Cr - 128)); - G = clampTo8bit(Y - 0.3441363 * (Cb - 128) - 0.71413636 * (Cr - 128)); - B = clampTo8bit(Y + 1.772 * (Cb - 128)); - - data[i ] = R; - data[i + 1] = G; - data[i + 2] = B; - } - } - break; - case 4: - // The default transform for four components is false - colorTransform = false; - // The adobe transform marker overrides any previous setting - if (this.adobe && this.adobe.transformCode) - colorTransform = true; - else if (typeof this.colorTransform !== 'undefined') - colorTransform = !!this.colorTransform; - - if (colorTransform) { - for (i = 0; i < dataLength; i += numComponents) { - Y = data[i]; - Cb = data[i + 1]; - Cr = data[i + 2]; - - C = 255 - clampTo8bit(Y + 1.402 * (Cr - 128)); - M = 255 - clampTo8bit(Y - 0.3441363 * (Cb - 128) - 0.71413636 * (Cr - 128)); - Ye = 255 - clampTo8bit(Y + 1.772 * (Cb - 128)); - - data[i ] = C; - data[i + 1] = M; - data[i + 2] = Ye; - // K is unchanged - } - } - break; - default: - throw 'Unsupported color mode'; - } - return data; - }, - copyToImageData: function copyToImageData(imageData) { - var width = imageData.width, height = imageData.height; - var imageDataBytes = width * height * 4; - var imageDataArray = imageData.data; - var data = this.getData(width, height); - var i = 0, j = 0; - var Y, K, C, M, R, G, B; - switch (this.components.length) { - case 1: - while (j < imageDataBytes) { - Y = data[i++]; - - imageDataArray[j++] = Y; - imageDataArray[j++] = Y; - imageDataArray[j++] = Y; - imageDataArray[j++] = 255; - } - break; - case 3: - while (j < imageDataBytes) { - R = data[i++]; - G = data[i++]; - B = data[i++]; - - imageDataArray[j++] = R; - imageDataArray[j++] = G; - imageDataArray[j++] = B; - imageDataArray[j++] = 255; - } - break; - case 4: - while (j < imageDataBytes) { - C = data[i++]; - M = data[i++]; - Y = data[i++]; - K = data[i++]; - - R = 255 - clampTo8bit(C * (1 - K / 255) + K); - G = 255 - clampTo8bit(M * (1 - K / 255) + K); - B = 255 - clampTo8bit(Y * (1 - K / 255) + K); - - imageDataArray[j++] = R; - imageDataArray[j++] = G; - imageDataArray[j++] = B; - imageDataArray[j++] = 255; - } - break; - default: - throw 'Unsupported color mode'; - } + return str; } }; - return constructor; + return MurmurHash3_64; })(); |
