From 83b02062f91d616a01a5daa035fbca7d097adc05 Mon Sep 17 00:00:00 2001 From: schneefux Date: Tue, 10 Jun 2014 15:24:45 +0200 Subject: aktualisiere PDF.js --- js/pdf.js | 3994 ++++++++++++++++++++++--------------------------------------- 1 file changed, 1458 insertions(+), 2536 deletions(-) (limited to 'js/pdf.js') diff --git a/js/pdf.js b/js/pdf.js index 421c29a..6d7416f 100644 --- a/js/pdf.js +++ b/js/pdf.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(value) { + return new Promise(function (resolve) { resolve(value); }); + }; + + /** + * Creates rejected promise + * @param reason rejection value * @returns {Promise} */ - Promise.resolve = function Promise_resolve(x) { - return new Promise(function (resolve) { resolve(x); }); + 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,1710 +1450,115 @@ 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 - }); - }); - action[0].call(action[1], data.data, deferred); - } else { - action[0].call(action[1], data.data); - } - } else { - error('Unkown action from worker: ' + data.action); - } - }; -} - -MessageHandler.prototype = { - on: function messageHandlerOn(actionName, handler, scope) { - var ah = this.actionHandler; - if (ah[actionName]) { - error('There is already an actionName called "' + actionName + '"'); - } - ah[actionName] = [handler, scope]; - }, - /** - * 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) { - var message = { - action: actionName, - data: data - }; - if (callback) { - var callbackId = this.callbackIndex++; - this.callbacks[callbackId] = callback; - message.callbackId = callbackId; - } - if (transfers && this.postMessageTransfers) { - this.comObj.postMessage(message, transfers); - } else { - this.comObj.postMessage(message); - } - } -}; - -function loadJpegStream(id, imageUrl, objs) { - var img = new Image(); - img.onload = (function loadJpegStream_onloadClosure() { - objs.resolve(id, img); - }); - img.src = imageUrl; -} - - -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++; + callbackId: data.callbackId, + data: result + }); + }, function (reason) { + comObj.postMessage({ + isReply: true, + callbackId: data.callbackId, + error: reason + }); + }); + } else { + action[0].call(action[1], data.data); } + } else { + error('Unknown action from worker: ' + data.action); } }; - 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; - } +MessageHandler.prototype = { + on: function messageHandlerOn(actionName, handler, scope) { + var ah = this.actionHandler; + if (ah[actionName]) { + error('There is already an actionName called "' + actionName + '"'); + } + ah[actionName] = [handler, scope]; + }, + /** + * 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 {Array} [transfers] Optional list of transfers/ArrayBuffers + */ + send: function messageHandlerSend(actionName, data, transfers) { + var message = { + action: actionName, + data: data + }; + 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 { + this.comObj.postMessage(message); } - }; - 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; -})(); +function loadJpegStream(id, imageUrl, objs) { + var img = new Image(); + img.onload = (function loadJpegStream_onloadClosure() { + objs.resolve(id, img); + }); + img.src = imageUrl; +} +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 @@ -3896,6 +2455,13 @@ PDFJS.postMessageTransfers = (PDFJS.postMessageTransfers === undefined ? 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: @@ -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; }; /** @@ -4033,15 +2600,19 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() { getDestinations: function PDFDocumentProxy_getDestinations() { 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. @@ -4113,16 +2672,52 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() { return PDFDocumentProxy; })(); +/** + * 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; - - 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]; + 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 (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); + var simpleFillText = + current.textRenderingMode === TextRenderingMode.FILL && + !font.disableFontFace; - ctx.scale(textHScale, 1); + ctx.save(); + ctx.transform.apply(ctx, current.textMatrix); + ctx.translate(current.x, current.y + current.textRise); - if (textSelection) { - this.save(); - ctx.scale(1, -1); - geom = this.createTextGeometry(); - this.restore(); - } - for (var i = 0; i < glyphsLength; ++i) { - - 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(); + 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; + + width = vmetric ? -vmetric[0] : width; + scaledX = vx / fontSizeScale; + scaledY = (x + vy) / fontSizeScale; } else { - lineWidth /= scale; - } - - if (textSelection) { - geom = this.createTextGeometry(); + 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 -- cgit v1.3.1