michael@0: /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ michael@0: /* Copyright 2012 Mozilla Foundation michael@0: * michael@0: * Licensed under the Apache License, Version 2.0 (the "License"); michael@0: * you may not use this file except in compliance with the License. michael@0: * You may obtain a copy of the License at michael@0: * michael@0: * http://www.apache.org/licenses/LICENSE-2.0 michael@0: * michael@0: * Unless required by applicable law or agreed to in writing, software michael@0: * distributed under the License is distributed on an "AS IS" BASIS, michael@0: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. michael@0: * See the License for the specific language governing permissions and michael@0: * limitations under the License. michael@0: */ michael@0: /*jshint globalstrict: false */ michael@0: michael@0: // Initializing PDFJS global object (if still undefined) michael@0: if (typeof PDFJS === 'undefined') { michael@0: (typeof window !== 'undefined' ? window : this).PDFJS = {}; michael@0: } michael@0: michael@0: PDFJS.version = '1.0.68'; michael@0: PDFJS.build = 'ead4cbf'; michael@0: michael@0: (function pdfjsWrapper() { michael@0: // Use strict in our context only - users might not want it michael@0: 'use strict'; michael@0: michael@0: /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ michael@0: /* Copyright 2012 Mozilla Foundation michael@0: * michael@0: * Licensed under the Apache License, Version 2.0 (the "License"); michael@0: * you may not use this file except in compliance with the License. michael@0: * You may obtain a copy of the License at michael@0: * michael@0: * http://www.apache.org/licenses/LICENSE-2.0 michael@0: * michael@0: * Unless required by applicable law or agreed to in writing, software michael@0: * distributed under the License is distributed on an "AS IS" BASIS, michael@0: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. michael@0: * See the License for the specific language governing permissions and michael@0: * limitations under the License. michael@0: */ michael@0: /* globals Cmd, ColorSpace, Dict, MozBlobBuilder, Name, PDFJS, Ref, URL, michael@0: Promise */ michael@0: michael@0: 'use strict'; michael@0: michael@0: var globalScope = (typeof window === 'undefined') ? this : window; michael@0: michael@0: var isWorker = (typeof window == 'undefined'); michael@0: michael@0: var FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0]; michael@0: michael@0: var TextRenderingMode = { michael@0: FILL: 0, michael@0: STROKE: 1, michael@0: FILL_STROKE: 2, michael@0: INVISIBLE: 3, michael@0: FILL_ADD_TO_PATH: 4, michael@0: STROKE_ADD_TO_PATH: 5, michael@0: FILL_STROKE_ADD_TO_PATH: 6, michael@0: ADD_TO_PATH: 7, michael@0: FILL_STROKE_MASK: 3, michael@0: ADD_TO_PATH_FLAG: 4 michael@0: }; michael@0: michael@0: var ImageKind = { michael@0: GRAYSCALE_1BPP: 1, michael@0: RGB_24BPP: 2, michael@0: RGBA_32BPP: 3 michael@0: }; michael@0: michael@0: // The global PDFJS object exposes the API michael@0: // In production, it will be declared outside a global wrapper michael@0: // In development, it will be declared here michael@0: if (!globalScope.PDFJS) { michael@0: globalScope.PDFJS = {}; michael@0: } michael@0: michael@0: globalScope.PDFJS.pdfBug = false; michael@0: michael@0: PDFJS.VERBOSITY_LEVELS = { michael@0: errors: 0, michael@0: warnings: 1, michael@0: infos: 5 michael@0: }; michael@0: michael@0: // All the possible operations for an operator list. michael@0: var OPS = PDFJS.OPS = { michael@0: // Intentionally start from 1 so it is easy to spot bad operators that will be michael@0: // 0's. michael@0: dependency: 1, michael@0: setLineWidth: 2, michael@0: setLineCap: 3, michael@0: setLineJoin: 4, michael@0: setMiterLimit: 5, michael@0: setDash: 6, michael@0: setRenderingIntent: 7, michael@0: setFlatness: 8, michael@0: setGState: 9, michael@0: save: 10, michael@0: restore: 11, michael@0: transform: 12, michael@0: moveTo: 13, michael@0: lineTo: 14, michael@0: curveTo: 15, michael@0: curveTo2: 16, michael@0: curveTo3: 17, michael@0: closePath: 18, michael@0: rectangle: 19, michael@0: stroke: 20, michael@0: closeStroke: 21, michael@0: fill: 22, michael@0: eoFill: 23, michael@0: fillStroke: 24, michael@0: eoFillStroke: 25, michael@0: closeFillStroke: 26, michael@0: closeEOFillStroke: 27, michael@0: endPath: 28, michael@0: clip: 29, michael@0: eoClip: 30, michael@0: beginText: 31, michael@0: endText: 32, michael@0: setCharSpacing: 33, michael@0: setWordSpacing: 34, michael@0: setHScale: 35, michael@0: setLeading: 36, michael@0: setFont: 37, michael@0: setTextRenderingMode: 38, michael@0: setTextRise: 39, michael@0: moveText: 40, michael@0: setLeadingMoveText: 41, michael@0: setTextMatrix: 42, michael@0: nextLine: 43, michael@0: showText: 44, michael@0: showSpacedText: 45, michael@0: nextLineShowText: 46, michael@0: nextLineSetSpacingShowText: 47, michael@0: setCharWidth: 48, michael@0: setCharWidthAndBounds: 49, michael@0: setStrokeColorSpace: 50, michael@0: setFillColorSpace: 51, michael@0: setStrokeColor: 52, michael@0: setStrokeColorN: 53, michael@0: setFillColor: 54, michael@0: setFillColorN: 55, michael@0: setStrokeGray: 56, michael@0: setFillGray: 57, michael@0: setStrokeRGBColor: 58, michael@0: setFillRGBColor: 59, michael@0: setStrokeCMYKColor: 60, michael@0: setFillCMYKColor: 61, michael@0: shadingFill: 62, michael@0: beginInlineImage: 63, michael@0: beginImageData: 64, michael@0: endInlineImage: 65, michael@0: paintXObject: 66, michael@0: markPoint: 67, michael@0: markPointProps: 68, michael@0: beginMarkedContent: 69, michael@0: beginMarkedContentProps: 70, michael@0: endMarkedContent: 71, michael@0: beginCompat: 72, michael@0: endCompat: 73, michael@0: paintFormXObjectBegin: 74, michael@0: paintFormXObjectEnd: 75, michael@0: beginGroup: 76, michael@0: endGroup: 77, michael@0: beginAnnotations: 78, michael@0: endAnnotations: 79, michael@0: beginAnnotation: 80, michael@0: endAnnotation: 81, michael@0: paintJpegXObject: 82, michael@0: paintImageMaskXObject: 83, michael@0: paintImageMaskXObjectGroup: 84, michael@0: paintImageXObject: 85, michael@0: paintInlineImageXObject: 86, michael@0: paintInlineImageXObjectGroup: 87, michael@0: paintImageXObjectRepeat: 88, michael@0: paintImageMaskXObjectRepeat: 89, michael@0: paintSolidColorImageMask: 90 michael@0: }; michael@0: michael@0: // A notice for devs. These are good for things that are helpful to devs, such michael@0: // as warning that Workers were disabled, which is important to devs but not michael@0: // end users. michael@0: function info(msg) { michael@0: if (PDFJS.verbosity >= PDFJS.VERBOSITY_LEVELS.infos) { michael@0: console.log('Info: ' + msg); michael@0: } michael@0: } michael@0: michael@0: // Non-fatal warnings. michael@0: function warn(msg) { michael@0: if (PDFJS.verbosity >= PDFJS.VERBOSITY_LEVELS.warnings) { michael@0: console.log('Warning: ' + msg); michael@0: } michael@0: } michael@0: michael@0: // Fatal errors that should trigger the fallback UI and halt execution by michael@0: // throwing an exception. michael@0: function error(msg) { michael@0: // If multiple arguments were passed, pass them all to the log function. michael@0: if (arguments.length > 1) { michael@0: var logArguments = ['Error:']; michael@0: logArguments.push.apply(logArguments, arguments); michael@0: console.log.apply(console, logArguments); michael@0: // Join the arguments into a single string for the lines below. michael@0: msg = [].join.call(arguments, ' '); michael@0: } else { michael@0: console.log('Error: ' + msg); michael@0: } michael@0: console.log(backtrace()); michael@0: UnsupportedManager.notify(UNSUPPORTED_FEATURES.unknown); michael@0: throw new Error(msg); michael@0: } michael@0: michael@0: function backtrace() { michael@0: try { michael@0: throw new Error(); michael@0: } catch (e) { michael@0: return e.stack ? e.stack.split('\n').slice(2).join('\n') : ''; michael@0: } michael@0: } michael@0: michael@0: function assert(cond, msg) { michael@0: if (!cond) { michael@0: error(msg); michael@0: } michael@0: } michael@0: michael@0: var UNSUPPORTED_FEATURES = PDFJS.UNSUPPORTED_FEATURES = { michael@0: unknown: 'unknown', michael@0: forms: 'forms', michael@0: javaScript: 'javaScript', michael@0: smask: 'smask', michael@0: shadingPattern: 'shadingPattern', michael@0: font: 'font' michael@0: }; michael@0: michael@0: var UnsupportedManager = PDFJS.UnsupportedManager = michael@0: (function UnsupportedManagerClosure() { michael@0: var listeners = []; michael@0: return { michael@0: listen: function (cb) { michael@0: listeners.push(cb); michael@0: }, michael@0: notify: function (featureId) { michael@0: warn('Unsupported feature "' + featureId + '"'); michael@0: for (var i = 0, ii = listeners.length; i < ii; i++) { michael@0: listeners[i](featureId); michael@0: } michael@0: } michael@0: }; michael@0: })(); michael@0: michael@0: // Combines two URLs. The baseUrl shall be absolute URL. If the url is an michael@0: // absolute URL, it will be returned as is. michael@0: function combineUrl(baseUrl, url) { michael@0: if (!url) { michael@0: return baseUrl; michael@0: } michael@0: if (/^[a-z][a-z0-9+\-.]*:/i.test(url)) { michael@0: return url; michael@0: } michael@0: var i; michael@0: if (url.charAt(0) == '/') { michael@0: // absolute path michael@0: i = baseUrl.indexOf('://'); michael@0: if (url.charAt(1) === '/') { michael@0: ++i; michael@0: } else { michael@0: i = baseUrl.indexOf('/', i + 3); michael@0: } michael@0: return baseUrl.substring(0, i) + url; michael@0: } else { michael@0: // relative path michael@0: var pathLength = baseUrl.length; michael@0: i = baseUrl.lastIndexOf('#'); michael@0: pathLength = i >= 0 ? i : pathLength; michael@0: i = baseUrl.lastIndexOf('?', pathLength); michael@0: pathLength = i >= 0 ? i : pathLength; michael@0: var prefixLength = baseUrl.lastIndexOf('/', pathLength); michael@0: return baseUrl.substring(0, prefixLength + 1) + url; michael@0: } michael@0: } michael@0: michael@0: // Validates if URL is safe and allowed, e.g. to avoid XSS. michael@0: function isValidUrl(url, allowRelative) { michael@0: if (!url) { michael@0: return false; michael@0: } michael@0: // RFC 3986 (http://tools.ietf.org/html/rfc3986#section-3.1) michael@0: // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) michael@0: var protocol = /^[a-z][a-z0-9+\-.]*(?=:)/i.exec(url); michael@0: if (!protocol) { michael@0: return allowRelative; michael@0: } michael@0: protocol = protocol[0].toLowerCase(); michael@0: switch (protocol) { michael@0: case 'http': michael@0: case 'https': michael@0: case 'ftp': michael@0: case 'mailto': michael@0: return true; michael@0: default: michael@0: return false; michael@0: } michael@0: } michael@0: PDFJS.isValidUrl = isValidUrl; michael@0: michael@0: function shadow(obj, prop, value) { michael@0: Object.defineProperty(obj, prop, { value: value, michael@0: enumerable: true, michael@0: configurable: true, michael@0: writable: false }); michael@0: return value; michael@0: } michael@0: michael@0: var PasswordResponses = PDFJS.PasswordResponses = { michael@0: NEED_PASSWORD: 1, michael@0: INCORRECT_PASSWORD: 2 michael@0: }; michael@0: michael@0: var PasswordException = (function PasswordExceptionClosure() { michael@0: function PasswordException(msg, code) { michael@0: this.name = 'PasswordException'; michael@0: this.message = msg; michael@0: this.code = code; michael@0: } michael@0: michael@0: PasswordException.prototype = new Error(); michael@0: PasswordException.constructor = PasswordException; michael@0: michael@0: return PasswordException; michael@0: })(); michael@0: michael@0: var UnknownErrorException = (function UnknownErrorExceptionClosure() { michael@0: function UnknownErrorException(msg, details) { michael@0: this.name = 'UnknownErrorException'; michael@0: this.message = msg; michael@0: this.details = details; michael@0: } michael@0: michael@0: UnknownErrorException.prototype = new Error(); michael@0: UnknownErrorException.constructor = UnknownErrorException; michael@0: michael@0: return UnknownErrorException; michael@0: })(); michael@0: michael@0: var InvalidPDFException = (function InvalidPDFExceptionClosure() { michael@0: function InvalidPDFException(msg) { michael@0: this.name = 'InvalidPDFException'; michael@0: this.message = msg; michael@0: } michael@0: michael@0: InvalidPDFException.prototype = new Error(); michael@0: InvalidPDFException.constructor = InvalidPDFException; michael@0: michael@0: return InvalidPDFException; michael@0: })(); michael@0: michael@0: var MissingPDFException = (function MissingPDFExceptionClosure() { michael@0: function MissingPDFException(msg) { michael@0: this.name = 'MissingPDFException'; michael@0: this.message = msg; michael@0: } michael@0: michael@0: MissingPDFException.prototype = new Error(); michael@0: MissingPDFException.constructor = MissingPDFException; michael@0: michael@0: return MissingPDFException; michael@0: })(); michael@0: michael@0: var NotImplementedException = (function NotImplementedExceptionClosure() { michael@0: function NotImplementedException(msg) { michael@0: this.message = msg; michael@0: } michael@0: michael@0: NotImplementedException.prototype = new Error(); michael@0: NotImplementedException.prototype.name = 'NotImplementedException'; michael@0: NotImplementedException.constructor = NotImplementedException; michael@0: michael@0: return NotImplementedException; michael@0: })(); michael@0: michael@0: var MissingDataException = (function MissingDataExceptionClosure() { michael@0: function MissingDataException(begin, end) { michael@0: this.begin = begin; michael@0: this.end = end; michael@0: this.message = 'Missing data [' + begin + ', ' + end + ')'; michael@0: } michael@0: michael@0: MissingDataException.prototype = new Error(); michael@0: MissingDataException.prototype.name = 'MissingDataException'; michael@0: MissingDataException.constructor = MissingDataException; michael@0: michael@0: return MissingDataException; michael@0: })(); michael@0: michael@0: var XRefParseException = (function XRefParseExceptionClosure() { michael@0: function XRefParseException(msg) { michael@0: this.message = msg; michael@0: } michael@0: michael@0: XRefParseException.prototype = new Error(); michael@0: XRefParseException.prototype.name = 'XRefParseException'; michael@0: XRefParseException.constructor = XRefParseException; michael@0: michael@0: return XRefParseException; michael@0: })(); michael@0: michael@0: michael@0: function bytesToString(bytes) { michael@0: var length = bytes.length; michael@0: var MAX_ARGUMENT_COUNT = 8192; michael@0: if (length < MAX_ARGUMENT_COUNT) { michael@0: return String.fromCharCode.apply(null, bytes); michael@0: } michael@0: var strBuf = []; michael@0: for (var i = 0; i < length; i += MAX_ARGUMENT_COUNT) { michael@0: var chunkEnd = Math.min(i + MAX_ARGUMENT_COUNT, length); michael@0: var chunk = bytes.subarray(i, chunkEnd); michael@0: strBuf.push(String.fromCharCode.apply(null, chunk)); michael@0: } michael@0: return strBuf.join(''); michael@0: } michael@0: michael@0: function stringToArray(str) { michael@0: var length = str.length; michael@0: var array = []; michael@0: for (var i = 0; i < length; ++i) { michael@0: array[i] = str.charCodeAt(i); michael@0: } michael@0: return array; michael@0: } michael@0: michael@0: function stringToBytes(str) { michael@0: var length = str.length; michael@0: var bytes = new Uint8Array(length); michael@0: for (var i = 0; i < length; ++i) { michael@0: bytes[i] = str.charCodeAt(i) & 0xFF; michael@0: } michael@0: return bytes; michael@0: } michael@0: michael@0: function string32(value) { michael@0: return String.fromCharCode((value >> 24) & 0xff, (value >> 16) & 0xff, michael@0: (value >> 8) & 0xff, value & 0xff); michael@0: } michael@0: michael@0: function log2(x) { michael@0: var n = 1, i = 0; michael@0: while (x > n) { michael@0: n <<= 1; michael@0: i++; michael@0: } michael@0: return i; michael@0: } michael@0: michael@0: function readInt8(data, start) { michael@0: return (data[start] << 24) >> 24; michael@0: } michael@0: michael@0: function readUint16(data, offset) { michael@0: return (data[offset] << 8) | data[offset + 1]; michael@0: } michael@0: michael@0: function readUint32(data, offset) { michael@0: return ((data[offset] << 24) | (data[offset + 1] << 16) | michael@0: (data[offset + 2] << 8) | data[offset + 3]) >>> 0; michael@0: } michael@0: michael@0: // Lazy test the endianness of the platform michael@0: // NOTE: This will be 'true' for simulated TypedArrays michael@0: function isLittleEndian() { michael@0: var buffer8 = new Uint8Array(2); michael@0: buffer8[0] = 1; michael@0: var buffer16 = new Uint16Array(buffer8.buffer); michael@0: return (buffer16[0] === 1); michael@0: } michael@0: michael@0: Object.defineProperty(PDFJS, 'isLittleEndian', { michael@0: configurable: true, michael@0: get: function PDFJS_isLittleEndian() { michael@0: return shadow(PDFJS, 'isLittleEndian', isLittleEndian()); michael@0: } michael@0: }); michael@0: michael@0: PDFJS.hasCanvasTypedArrays = true; michael@0: michael@0: var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0]; michael@0: michael@0: var Util = PDFJS.Util = (function UtilClosure() { michael@0: function Util() {} michael@0: michael@0: Util.makeCssRgb = function Util_makeCssRgb(rgb) { michael@0: return 'rgb(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ')'; michael@0: }; michael@0: michael@0: Util.makeCssCmyk = function Util_makeCssCmyk(cmyk) { michael@0: var rgb = ColorSpace.singletons.cmyk.getRgb(cmyk, 0); michael@0: return Util.makeCssRgb(rgb); michael@0: }; michael@0: michael@0: // Concatenates two transformation matrices together and returns the result. michael@0: Util.transform = function Util_transform(m1, m2) { michael@0: return [ michael@0: m1[0] * m2[0] + m1[2] * m2[1], michael@0: m1[1] * m2[0] + m1[3] * m2[1], michael@0: m1[0] * m2[2] + m1[2] * m2[3], michael@0: m1[1] * m2[2] + m1[3] * m2[3], michael@0: m1[0] * m2[4] + m1[2] * m2[5] + m1[4], michael@0: m1[1] * m2[4] + m1[3] * m2[5] + m1[5] michael@0: ]; michael@0: }; michael@0: michael@0: // For 2d affine transforms michael@0: Util.applyTransform = function Util_applyTransform(p, m) { michael@0: var xt = p[0] * m[0] + p[1] * m[2] + m[4]; michael@0: var yt = p[0] * m[1] + p[1] * m[3] + m[5]; michael@0: return [xt, yt]; michael@0: }; michael@0: michael@0: Util.applyInverseTransform = function Util_applyInverseTransform(p, m) { michael@0: var d = m[0] * m[3] - m[1] * m[2]; michael@0: var xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d; michael@0: var yt = (-p[0] * m[1] + p[1] * m[0] + m[4] * m[1] - m[5] * m[0]) / d; michael@0: return [xt, yt]; michael@0: }; michael@0: michael@0: // Applies the transform to the rectangle and finds the minimum axially michael@0: // aligned bounding box. michael@0: Util.getAxialAlignedBoundingBox = michael@0: function Util_getAxialAlignedBoundingBox(r, m) { michael@0: michael@0: var p1 = Util.applyTransform(r, m); michael@0: var p2 = Util.applyTransform(r.slice(2, 4), m); michael@0: var p3 = Util.applyTransform([r[0], r[3]], m); michael@0: var p4 = Util.applyTransform([r[2], r[1]], m); michael@0: return [ michael@0: Math.min(p1[0], p2[0], p3[0], p4[0]), michael@0: Math.min(p1[1], p2[1], p3[1], p4[1]), michael@0: Math.max(p1[0], p2[0], p3[0], p4[0]), michael@0: Math.max(p1[1], p2[1], p3[1], p4[1]) michael@0: ]; michael@0: }; michael@0: michael@0: Util.inverseTransform = function Util_inverseTransform(m) { michael@0: var d = m[0] * m[3] - m[1] * m[2]; michael@0: return [m[3] / d, -m[1] / d, -m[2] / d, m[0] / d, michael@0: (m[2] * m[5] - m[4] * m[3]) / d, (m[4] * m[1] - m[5] * m[0]) / d]; michael@0: }; michael@0: michael@0: // Apply a generic 3d matrix M on a 3-vector v: michael@0: // | a b c | | X | michael@0: // | d e f | x | Y | michael@0: // | g h i | | Z | michael@0: // M is assumed to be serialized as [a,b,c,d,e,f,g,h,i], michael@0: // with v as [X,Y,Z] michael@0: Util.apply3dTransform = function Util_apply3dTransform(m, v) { michael@0: return [ michael@0: m[0] * v[0] + m[1] * v[1] + m[2] * v[2], michael@0: m[3] * v[0] + m[4] * v[1] + m[5] * v[2], michael@0: m[6] * v[0] + m[7] * v[1] + m[8] * v[2] michael@0: ]; michael@0: }; michael@0: michael@0: // This calculation uses Singular Value Decomposition. michael@0: // The SVD can be represented with formula A = USV. We are interested in the michael@0: // matrix S here because it represents the scale values. michael@0: Util.singularValueDecompose2dScale = michael@0: function Util_singularValueDecompose2dScale(m) { michael@0: michael@0: var transpose = [m[0], m[2], m[1], m[3]]; michael@0: michael@0: // Multiply matrix m with its transpose. michael@0: var a = m[0] * transpose[0] + m[1] * transpose[2]; michael@0: var b = m[0] * transpose[1] + m[1] * transpose[3]; michael@0: var c = m[2] * transpose[0] + m[3] * transpose[2]; michael@0: var d = m[2] * transpose[1] + m[3] * transpose[3]; michael@0: michael@0: // Solve the second degree polynomial to get roots. michael@0: var first = (a + d) / 2; michael@0: var second = Math.sqrt((a + d) * (a + d) - 4 * (a * d - c * b)) / 2; michael@0: var sx = first + second || 1; michael@0: var sy = first - second || 1; michael@0: michael@0: // Scale values are the square roots of the eigenvalues. michael@0: return [Math.sqrt(sx), Math.sqrt(sy)]; michael@0: }; michael@0: michael@0: // Normalize rectangle rect=[x1, y1, x2, y2] so that (x1,y1) < (x2,y2) michael@0: // For coordinate systems whose origin lies in the bottom-left, this michael@0: // means normalization to (BL,TR) ordering. For systems with origin in the michael@0: // top-left, this means (TL,BR) ordering. michael@0: Util.normalizeRect = function Util_normalizeRect(rect) { michael@0: var r = rect.slice(0); // clone rect michael@0: if (rect[0] > rect[2]) { michael@0: r[0] = rect[2]; michael@0: r[2] = rect[0]; michael@0: } michael@0: if (rect[1] > rect[3]) { michael@0: r[1] = rect[3]; michael@0: r[3] = rect[1]; michael@0: } michael@0: return r; michael@0: }; michael@0: michael@0: // Returns a rectangle [x1, y1, x2, y2] corresponding to the michael@0: // intersection of rect1 and rect2. If no intersection, returns 'false' michael@0: // The rectangle coordinates of rect1, rect2 should be [x1, y1, x2, y2] michael@0: Util.intersect = function Util_intersect(rect1, rect2) { michael@0: function compare(a, b) { michael@0: return a - b; michael@0: } michael@0: michael@0: // Order points along the axes michael@0: var orderedX = [rect1[0], rect1[2], rect2[0], rect2[2]].sort(compare), michael@0: orderedY = [rect1[1], rect1[3], rect2[1], rect2[3]].sort(compare), michael@0: result = []; michael@0: michael@0: rect1 = Util.normalizeRect(rect1); michael@0: rect2 = Util.normalizeRect(rect2); michael@0: michael@0: // X: first and second points belong to different rectangles? michael@0: if ((orderedX[0] === rect1[0] && orderedX[1] === rect2[0]) || michael@0: (orderedX[0] === rect2[0] && orderedX[1] === rect1[0])) { michael@0: // Intersection must be between second and third points michael@0: result[0] = orderedX[1]; michael@0: result[2] = orderedX[2]; michael@0: } else { michael@0: return false; michael@0: } michael@0: michael@0: // Y: first and second points belong to different rectangles? michael@0: if ((orderedY[0] === rect1[1] && orderedY[1] === rect2[1]) || michael@0: (orderedY[0] === rect2[1] && orderedY[1] === rect1[1])) { michael@0: // Intersection must be between second and third points michael@0: result[1] = orderedY[1]; michael@0: result[3] = orderedY[2]; michael@0: } else { michael@0: return false; michael@0: } michael@0: michael@0: return result; michael@0: }; michael@0: michael@0: Util.sign = function Util_sign(num) { michael@0: return num < 0 ? -1 : 1; michael@0: }; michael@0: michael@0: // TODO(mack): Rename appendToArray michael@0: Util.concatenateToArray = function concatenateToArray(arr1, arr2) { michael@0: Array.prototype.push.apply(arr1, arr2); michael@0: }; michael@0: michael@0: Util.prependToArray = function concatenateToArray(arr1, arr2) { michael@0: Array.prototype.unshift.apply(arr1, arr2); michael@0: }; michael@0: michael@0: Util.extendObj = function extendObj(obj1, obj2) { michael@0: for (var key in obj2) { michael@0: obj1[key] = obj2[key]; michael@0: } michael@0: }; michael@0: michael@0: Util.getInheritableProperty = function Util_getInheritableProperty(dict, michael@0: name) { michael@0: while (dict && !dict.has(name)) { michael@0: dict = dict.get('Parent'); michael@0: } michael@0: if (!dict) { michael@0: return null; michael@0: } michael@0: return dict.get(name); michael@0: }; michael@0: michael@0: Util.inherit = function Util_inherit(sub, base, prototype) { michael@0: sub.prototype = Object.create(base.prototype); michael@0: sub.prototype.constructor = sub; michael@0: for (var prop in prototype) { michael@0: sub.prototype[prop] = prototype[prop]; michael@0: } michael@0: }; michael@0: michael@0: Util.loadScript = function Util_loadScript(src, callback) { michael@0: var script = document.createElement('script'); michael@0: var loaded = false; michael@0: script.setAttribute('src', src); michael@0: if (callback) { michael@0: script.onload = function() { michael@0: if (!loaded) { michael@0: callback(); michael@0: } michael@0: loaded = true; michael@0: }; michael@0: } michael@0: document.getElementsByTagName('head')[0].appendChild(script); michael@0: }; michael@0: michael@0: return Util; michael@0: })(); michael@0: michael@0: var PageViewport = PDFJS.PageViewport = (function PageViewportClosure() { michael@0: function PageViewport(viewBox, scale, rotation, offsetX, offsetY, dontFlip) { michael@0: this.viewBox = viewBox; michael@0: this.scale = scale; michael@0: this.rotation = rotation; michael@0: this.offsetX = offsetX; michael@0: this.offsetY = offsetY; michael@0: michael@0: // creating transform to convert pdf coordinate system to the normal michael@0: // canvas like coordinates taking in account scale and rotation michael@0: var centerX = (viewBox[2] + viewBox[0]) / 2; michael@0: var centerY = (viewBox[3] + viewBox[1]) / 2; michael@0: var rotateA, rotateB, rotateC, rotateD; michael@0: rotation = rotation % 360; michael@0: rotation = rotation < 0 ? rotation + 360 : rotation; michael@0: switch (rotation) { michael@0: case 180: michael@0: rotateA = -1; rotateB = 0; rotateC = 0; rotateD = 1; michael@0: break; michael@0: case 90: michael@0: rotateA = 0; rotateB = 1; rotateC = 1; rotateD = 0; michael@0: break; michael@0: case 270: michael@0: rotateA = 0; rotateB = -1; rotateC = -1; rotateD = 0; michael@0: break; michael@0: //case 0: michael@0: default: michael@0: rotateA = 1; rotateB = 0; rotateC = 0; rotateD = -1; michael@0: break; michael@0: } michael@0: michael@0: if (dontFlip) { michael@0: rotateC = -rotateC; rotateD = -rotateD; michael@0: } michael@0: michael@0: var offsetCanvasX, offsetCanvasY; michael@0: var width, height; michael@0: if (rotateA === 0) { michael@0: offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX; michael@0: offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY; michael@0: width = Math.abs(viewBox[3] - viewBox[1]) * scale; michael@0: height = Math.abs(viewBox[2] - viewBox[0]) * scale; michael@0: } else { michael@0: offsetCanvasX = Math.abs(centerX - viewBox[0]) * scale + offsetX; michael@0: offsetCanvasY = Math.abs(centerY - viewBox[1]) * scale + offsetY; michael@0: width = Math.abs(viewBox[2] - viewBox[0]) * scale; michael@0: height = Math.abs(viewBox[3] - viewBox[1]) * scale; michael@0: } michael@0: // creating transform for the following operations: michael@0: // translate(-centerX, -centerY), rotate and flip vertically, michael@0: // scale, and translate(offsetCanvasX, offsetCanvasY) michael@0: this.transform = [ michael@0: rotateA * scale, michael@0: rotateB * scale, michael@0: rotateC * scale, michael@0: rotateD * scale, michael@0: offsetCanvasX - rotateA * scale * centerX - rotateC * scale * centerY, michael@0: offsetCanvasY - rotateB * scale * centerX - rotateD * scale * centerY michael@0: ]; michael@0: michael@0: this.width = width; michael@0: this.height = height; michael@0: this.fontScale = scale; michael@0: } michael@0: PageViewport.prototype = { michael@0: clone: function PageViewPort_clone(args) { michael@0: args = args || {}; michael@0: var scale = 'scale' in args ? args.scale : this.scale; michael@0: var rotation = 'rotation' in args ? args.rotation : this.rotation; michael@0: return new PageViewport(this.viewBox.slice(), scale, rotation, michael@0: this.offsetX, this.offsetY, args.dontFlip); michael@0: }, michael@0: convertToViewportPoint: function PageViewport_convertToViewportPoint(x, y) { michael@0: return Util.applyTransform([x, y], this.transform); michael@0: }, michael@0: convertToViewportRectangle: michael@0: function PageViewport_convertToViewportRectangle(rect) { michael@0: var tl = Util.applyTransform([rect[0], rect[1]], this.transform); michael@0: var br = Util.applyTransform([rect[2], rect[3]], this.transform); michael@0: return [tl[0], tl[1], br[0], br[1]]; michael@0: }, michael@0: convertToPdfPoint: function PageViewport_convertToPdfPoint(x, y) { michael@0: return Util.applyInverseTransform([x, y], this.transform); michael@0: } michael@0: }; michael@0: return PageViewport; michael@0: })(); michael@0: michael@0: var PDFStringTranslateTable = [ michael@0: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, michael@0: 0x2D8, 0x2C7, 0x2C6, 0x2D9, 0x2DD, 0x2DB, 0x2DA, 0x2DC, 0, 0, 0, 0, 0, 0, 0, michael@0: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, michael@0: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, michael@0: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, michael@0: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2022, 0x2020, 0x2021, 0x2026, 0x2014, michael@0: 0x2013, 0x192, 0x2044, 0x2039, 0x203A, 0x2212, 0x2030, 0x201E, 0x201C, michael@0: 0x201D, 0x2018, 0x2019, 0x201A, 0x2122, 0xFB01, 0xFB02, 0x141, 0x152, 0x160, michael@0: 0x178, 0x17D, 0x131, 0x142, 0x153, 0x161, 0x17E, 0, 0x20AC michael@0: ]; michael@0: michael@0: function stringToPDFString(str) { michael@0: var i, n = str.length, strBuf = []; michael@0: if (str[0] === '\xFE' && str[1] === '\xFF') { michael@0: // UTF16BE BOM michael@0: for (i = 2; i < n; i += 2) { michael@0: strBuf.push(String.fromCharCode( michael@0: (str.charCodeAt(i) << 8) | str.charCodeAt(i + 1))); michael@0: } michael@0: } else { michael@0: for (i = 0; i < n; ++i) { michael@0: var code = PDFStringTranslateTable[str.charCodeAt(i)]; michael@0: strBuf.push(code ? String.fromCharCode(code) : str.charAt(i)); michael@0: } michael@0: } michael@0: return strBuf.join(''); michael@0: } michael@0: michael@0: function stringToUTF8String(str) { michael@0: return decodeURIComponent(escape(str)); michael@0: } michael@0: michael@0: function isEmptyObj(obj) { michael@0: for (var key in obj) { michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: function isBool(v) { michael@0: return typeof v == 'boolean'; michael@0: } michael@0: michael@0: function isInt(v) { michael@0: return typeof v == 'number' && ((v | 0) == v); michael@0: } michael@0: michael@0: function isNum(v) { michael@0: return typeof v == 'number'; michael@0: } michael@0: michael@0: function isString(v) { michael@0: return typeof v == 'string'; michael@0: } michael@0: michael@0: function isNull(v) { michael@0: return v === null; michael@0: } michael@0: michael@0: function isName(v) { michael@0: return v instanceof Name; michael@0: } michael@0: michael@0: function isCmd(v, cmd) { michael@0: return v instanceof Cmd && (!cmd || v.cmd == cmd); michael@0: } michael@0: michael@0: function isDict(v, type) { michael@0: if (!(v instanceof Dict)) { michael@0: return false; michael@0: } michael@0: if (!type) { michael@0: return true; michael@0: } michael@0: var dictType = v.get('Type'); michael@0: return isName(dictType) && dictType.name == type; michael@0: } michael@0: michael@0: function isArray(v) { michael@0: return v instanceof Array; michael@0: } michael@0: michael@0: function isStream(v) { michael@0: return typeof v == 'object' && v !== null && v !== undefined && michael@0: ('getBytes' in v); michael@0: } michael@0: michael@0: function isArrayBuffer(v) { michael@0: return typeof v == 'object' && v !== null && v !== undefined && michael@0: ('byteLength' in v); michael@0: } michael@0: michael@0: function isRef(v) { michael@0: return v instanceof Ref; michael@0: } michael@0: michael@0: function isPDFFunction(v) { michael@0: var fnDict; michael@0: if (typeof v != 'object') { michael@0: return false; michael@0: } else if (isDict(v)) { michael@0: fnDict = v; michael@0: } else if (isStream(v)) { michael@0: fnDict = v.dict; michael@0: } else { michael@0: return false; michael@0: } michael@0: return fnDict.has('FunctionType'); michael@0: } michael@0: michael@0: /** michael@0: * Legacy support for PDFJS Promise implementation. michael@0: * TODO remove eventually michael@0: * @ignore michael@0: */ michael@0: var LegacyPromise = PDFJS.LegacyPromise = (function LegacyPromiseClosure() { michael@0: return function LegacyPromise() { michael@0: var resolve, reject; michael@0: var promise = new Promise(function (resolve_, reject_) { michael@0: resolve = resolve_; michael@0: reject = reject_; michael@0: }); michael@0: promise.resolve = resolve; michael@0: promise.reject = reject; michael@0: return promise; michael@0: }; michael@0: })(); michael@0: michael@0: /** michael@0: * Polyfill for Promises: michael@0: * The following promise implementation tries to generally implment the michael@0: * Promise/A+ spec. Some notable differences from other promise libaries are: michael@0: * - There currently isn't a seperate deferred and promise object. michael@0: * - Unhandled rejections eventually show an error if they aren't handled. michael@0: * michael@0: * Based off of the work in: michael@0: * https://bugzilla.mozilla.org/show_bug.cgi?id=810490 michael@0: */ michael@0: (function PromiseClosure() { michael@0: if (globalScope.Promise) { michael@0: // Promises existing in the DOM/Worker, checking presence of all/resolve michael@0: if (typeof globalScope.Promise.all !== 'function') { michael@0: globalScope.Promise.all = function (iterable) { michael@0: var count = 0, results = [], resolve, reject; michael@0: var promise = new globalScope.Promise(function (resolve_, reject_) { michael@0: resolve = resolve_; michael@0: reject = reject_; michael@0: }); michael@0: iterable.forEach(function (p, i) { michael@0: count++; michael@0: p.then(function (result) { michael@0: results[i] = result; michael@0: count--; michael@0: if (count === 0) { michael@0: resolve(results); michael@0: } michael@0: }, reject); michael@0: }); michael@0: if (count === 0) { michael@0: resolve(results); michael@0: } michael@0: return promise; michael@0: }; michael@0: } michael@0: if (typeof globalScope.Promise.resolve !== 'function') { michael@0: globalScope.Promise.resolve = function (x) { michael@0: return new globalScope.Promise(function (resolve) { resolve(x); }); michael@0: }; michael@0: } michael@0: return; michael@0: } michael@0: throw new Error('DOM Promise is not present'); michael@0: })(); michael@0: michael@0: var StatTimer = (function StatTimerClosure() { michael@0: function rpad(str, pad, length) { michael@0: while (str.length < length) { michael@0: str += pad; michael@0: } michael@0: return str; michael@0: } michael@0: function StatTimer() { michael@0: this.started = {}; michael@0: this.times = []; michael@0: this.enabled = true; michael@0: } michael@0: StatTimer.prototype = { michael@0: time: function StatTimer_time(name) { michael@0: if (!this.enabled) { michael@0: return; michael@0: } michael@0: if (name in this.started) { michael@0: warn('Timer is already running for ' + name); michael@0: } michael@0: this.started[name] = Date.now(); michael@0: }, michael@0: timeEnd: function StatTimer_timeEnd(name) { michael@0: if (!this.enabled) { michael@0: return; michael@0: } michael@0: if (!(name in this.started)) { michael@0: warn('Timer has not been started for ' + name); michael@0: } michael@0: this.times.push({ michael@0: 'name': name, michael@0: 'start': this.started[name], michael@0: 'end': Date.now() michael@0: }); michael@0: // Remove timer from started so it can be called again. michael@0: delete this.started[name]; michael@0: }, michael@0: toString: function StatTimer_toString() { michael@0: var i, ii; michael@0: var times = this.times; michael@0: var out = ''; michael@0: // Find the longest name for padding purposes. michael@0: var longest = 0; michael@0: for (i = 0, ii = times.length; i < ii; ++i) { michael@0: var name = times[i]['name']; michael@0: if (name.length > longest) { michael@0: longest = name.length; michael@0: } michael@0: } michael@0: for (i = 0, ii = times.length; i < ii; ++i) { michael@0: var span = times[i]; michael@0: var duration = span.end - span.start; michael@0: out += rpad(span['name'], ' ', longest) + ' ' + duration + 'ms\n'; michael@0: } michael@0: return out; michael@0: } michael@0: }; michael@0: return StatTimer; michael@0: })(); michael@0: michael@0: PDFJS.createBlob = function createBlob(data, contentType) { michael@0: if (typeof Blob !== 'undefined') { michael@0: return new Blob([data], { type: contentType }); michael@0: } michael@0: // Blob builder is deprecated in FF14 and removed in FF18. michael@0: var bb = new MozBlobBuilder(); michael@0: bb.append(data); michael@0: return bb.getBlob(contentType); michael@0: }; michael@0: michael@0: PDFJS.createObjectURL = (function createObjectURLClosure() { michael@0: // Blob/createObjectURL is not available, falling back to data schema. michael@0: var digits = michael@0: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; michael@0: michael@0: return function createObjectURL(data, contentType) { michael@0: if (!PDFJS.disableCreateObjectURL && michael@0: typeof URL !== 'undefined' && URL.createObjectURL) { michael@0: var blob = PDFJS.createBlob(data, contentType); michael@0: return URL.createObjectURL(blob); michael@0: } michael@0: michael@0: var buffer = 'data:' + contentType + ';base64,'; michael@0: for (var i = 0, ii = data.length; i < ii; i += 3) { michael@0: var b1 = data[i] & 0xFF; michael@0: var b2 = data[i + 1] & 0xFF; michael@0: var b3 = data[i + 2] & 0xFF; michael@0: var d1 = b1 >> 2, d2 = ((b1 & 3) << 4) | (b2 >> 4); michael@0: var d3 = i + 1 < ii ? ((b2 & 0xF) << 2) | (b3 >> 6) : 64; michael@0: var d4 = i + 2 < ii ? (b3 & 0x3F) : 64; michael@0: buffer += digits[d1] + digits[d2] + digits[d3] + digits[d4]; michael@0: } michael@0: return buffer; michael@0: }; michael@0: })(); michael@0: michael@0: function MessageHandler(name, comObj) { michael@0: this.name = name; michael@0: this.comObj = comObj; michael@0: this.callbackIndex = 1; michael@0: this.postMessageTransfers = true; michael@0: var callbacks = this.callbacks = {}; michael@0: var ah = this.actionHandler = {}; michael@0: michael@0: ah['console_log'] = [function ahConsoleLog(data) { michael@0: console.log.apply(console, data); michael@0: }]; michael@0: ah['console_error'] = [function ahConsoleError(data) { michael@0: console.error.apply(console, data); michael@0: }]; michael@0: ah['_unsupported_feature'] = [function ah_unsupportedFeature(data) { michael@0: UnsupportedManager.notify(data); michael@0: }]; michael@0: michael@0: comObj.onmessage = function messageHandlerComObjOnMessage(event) { michael@0: var data = event.data; michael@0: if (data.isReply) { michael@0: var callbackId = data.callbackId; michael@0: if (data.callbackId in callbacks) { michael@0: var callback = callbacks[callbackId]; michael@0: delete callbacks[callbackId]; michael@0: callback(data.data); michael@0: } else { michael@0: error('Cannot resolve callback ' + callbackId); michael@0: } michael@0: } else if (data.action in ah) { michael@0: var action = ah[data.action]; michael@0: if (data.callbackId) { michael@0: var deferred = {}; michael@0: var promise = new Promise(function (resolve, reject) { michael@0: deferred.resolve = resolve; michael@0: deferred.reject = reject; michael@0: }); michael@0: deferred.promise = promise; michael@0: promise.then(function(resolvedData) { michael@0: comObj.postMessage({ michael@0: isReply: true, michael@0: callbackId: data.callbackId, michael@0: data: resolvedData michael@0: }); michael@0: }); michael@0: action[0].call(action[1], data.data, deferred); michael@0: } else { michael@0: action[0].call(action[1], data.data); michael@0: } michael@0: } else { michael@0: error('Unkown action from worker: ' + data.action); michael@0: } michael@0: }; michael@0: } michael@0: michael@0: MessageHandler.prototype = { michael@0: on: function messageHandlerOn(actionName, handler, scope) { michael@0: var ah = this.actionHandler; michael@0: if (ah[actionName]) { michael@0: error('There is already an actionName called "' + actionName + '"'); michael@0: } michael@0: ah[actionName] = [handler, scope]; michael@0: }, michael@0: /** michael@0: * Sends a message to the comObj to invoke the action with the supplied data. michael@0: * @param {String} actionName Action to call. michael@0: * @param {JSON} data JSON data to send. michael@0: * @param {function} [callback] Optional callback that will handle a reply. michael@0: * @param {Array} [transfers] Optional list of transfers/ArrayBuffers michael@0: */ michael@0: send: function messageHandlerSend(actionName, data, callback, transfers) { michael@0: var message = { michael@0: action: actionName, michael@0: data: data michael@0: }; michael@0: if (callback) { michael@0: var callbackId = this.callbackIndex++; michael@0: this.callbacks[callbackId] = callback; michael@0: message.callbackId = callbackId; michael@0: } michael@0: if (transfers && this.postMessageTransfers) { michael@0: this.comObj.postMessage(message, transfers); michael@0: } else { michael@0: this.comObj.postMessage(message); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: function loadJpegStream(id, imageUrl, objs) { michael@0: var img = new Image(); michael@0: img.onload = (function loadJpegStream_onloadClosure() { michael@0: objs.resolve(id, img); michael@0: }); michael@0: img.src = imageUrl; michael@0: } michael@0: michael@0: michael@0: var ColorSpace = (function ColorSpaceClosure() { michael@0: // Constructor should define this.numComps, this.defaultColor, this.name michael@0: function ColorSpace() { michael@0: error('should not call ColorSpace constructor'); michael@0: } michael@0: michael@0: ColorSpace.prototype = { michael@0: /** michael@0: * Converts the color value to the RGB color. The color components are michael@0: * located in the src array starting from the srcOffset. Returns the array michael@0: * of the rgb components, each value ranging from [0,255]. michael@0: */ michael@0: getRgb: function ColorSpace_getRgb(src, srcOffset) { michael@0: var rgb = new Uint8Array(3); michael@0: this.getRgbItem(src, srcOffset, rgb, 0); michael@0: return rgb; michael@0: }, michael@0: /** michael@0: * Converts the color value to the RGB color, similar to the getRgb method. michael@0: * The result placed into the dest array starting from the destOffset. michael@0: */ michael@0: getRgbItem: function ColorSpace_getRgbItem(src, srcOffset, michael@0: dest, destOffset) { michael@0: error('Should not call ColorSpace.getRgbItem'); michael@0: }, michael@0: /** michael@0: * Converts the specified number of the color values to the RGB colors. michael@0: * The colors are located in the src array starting from the srcOffset. michael@0: * The result is placed into the dest array starting from the destOffset. michael@0: * The src array items shall be in [0,2^bits) range, the dest array items michael@0: * will be in [0,255] range. alpha01 indicates how many alpha components michael@0: * there are in the dest array; it will be either 0 (RGB array) or 1 (RGBA michael@0: * array). michael@0: */ michael@0: getRgbBuffer: function ColorSpace_getRgbBuffer(src, srcOffset, count, michael@0: dest, destOffset, bits, michael@0: alpha01) { michael@0: error('Should not call ColorSpace.getRgbBuffer'); michael@0: }, michael@0: /** michael@0: * Determines the number of bytes required to store the result of the michael@0: * conversion done by the getRgbBuffer method. As in getRgbBuffer, michael@0: * |alpha01| is either 0 (RGB output) or 1 (RGBA output). michael@0: */ michael@0: getOutputLength: function ColorSpace_getOutputLength(inputLength, michael@0: alpha01) { michael@0: error('Should not call ColorSpace.getOutputLength'); michael@0: }, michael@0: /** michael@0: * Returns true if source data will be equal the result/output data. michael@0: */ michael@0: isPassthrough: function ColorSpace_isPassthrough(bits) { michael@0: return false; michael@0: }, michael@0: /** michael@0: * Fills in the RGB colors in the destination buffer. alpha01 indicates michael@0: * how many alpha components there are in the dest array; it will be either michael@0: * 0 (RGB array) or 1 (RGBA array). michael@0: */ michael@0: fillRgb: function ColorSpace_fillRgb(dest, originalWidth, michael@0: originalHeight, width, height, michael@0: actualHeight, bpc, comps, alpha01) { michael@0: var count = originalWidth * originalHeight; michael@0: var rgbBuf = null; michael@0: var numComponentColors = 1 << bpc; michael@0: var needsResizing = originalHeight != height || originalWidth != width; michael@0: var i, ii; michael@0: michael@0: if (this.isPassthrough(bpc)) { michael@0: rgbBuf = comps; michael@0: } else if (this.numComps === 1 && count > numComponentColors && michael@0: this.name !== 'DeviceGray' && this.name !== 'DeviceRGB') { michael@0: // Optimization: create a color map when there is just one component and michael@0: // we are converting more colors than the size of the color map. We michael@0: // don't build the map if the colorspace is gray or rgb since those michael@0: // methods are faster than building a map. This mainly offers big speed michael@0: // ups for indexed and alternate colorspaces. michael@0: // michael@0: // TODO it may be worth while to cache the color map. While running michael@0: // testing I never hit a cache so I will leave that out for now (perhaps michael@0: // we are reparsing colorspaces too much?). michael@0: var allColors = bpc <= 8 ? new Uint8Array(numComponentColors) : michael@0: new Uint16Array(numComponentColors); michael@0: var key; michael@0: for (i = 0; i < numComponentColors; i++) { michael@0: allColors[i] = i; michael@0: } michael@0: var colorMap = new Uint8Array(numComponentColors * 3); michael@0: this.getRgbBuffer(allColors, 0, numComponentColors, colorMap, 0, bpc, michael@0: /* alpha01 = */ 0); michael@0: michael@0: var destPos, rgbPos; michael@0: if (!needsResizing) { michael@0: // Fill in the RGB values directly into |dest|. michael@0: destPos = 0; michael@0: for (i = 0; i < count; ++i) { michael@0: key = comps[i] * 3; michael@0: dest[destPos++] = colorMap[key]; michael@0: dest[destPos++] = colorMap[key + 1]; michael@0: dest[destPos++] = colorMap[key + 2]; michael@0: destPos += alpha01; michael@0: } michael@0: } else { michael@0: rgbBuf = new Uint8Array(count * 3); michael@0: rgbPos = 0; michael@0: for (i = 0; i < count; ++i) { michael@0: key = comps[i] * 3; michael@0: rgbBuf[rgbPos++] = colorMap[key]; michael@0: rgbBuf[rgbPos++] = colorMap[key + 1]; michael@0: rgbBuf[rgbPos++] = colorMap[key + 2]; michael@0: } michael@0: } michael@0: } else { michael@0: if (!needsResizing) { michael@0: // Fill in the RGB values directly into |dest|. michael@0: this.getRgbBuffer(comps, 0, width * actualHeight, dest, 0, bpc, michael@0: alpha01); michael@0: } else { michael@0: rgbBuf = new Uint8Array(count * 3); michael@0: this.getRgbBuffer(comps, 0, count, rgbBuf, 0, bpc, michael@0: /* alpha01 = */ 0); michael@0: } michael@0: } michael@0: michael@0: if (rgbBuf) { michael@0: if (needsResizing) { michael@0: rgbBuf = PDFImage.resize(rgbBuf, bpc, 3, originalWidth, michael@0: originalHeight, width, height); michael@0: } michael@0: rgbPos = 0; michael@0: destPos = 0; michael@0: for (i = 0, ii = width * actualHeight; i < ii; i++) { michael@0: dest[destPos++] = rgbBuf[rgbPos++]; michael@0: dest[destPos++] = rgbBuf[rgbPos++]; michael@0: dest[destPos++] = rgbBuf[rgbPos++]; michael@0: destPos += alpha01; michael@0: } michael@0: } michael@0: }, michael@0: /** michael@0: * True if the colorspace has components in the default range of [0, 1]. michael@0: * This should be true for all colorspaces except for lab color spaces michael@0: * which are [0,100], [-128, 127], [-128, 127]. michael@0: */ michael@0: usesZeroToOneRange: true michael@0: }; michael@0: michael@0: ColorSpace.parse = function ColorSpace_parse(cs, xref, res) { michael@0: var IR = ColorSpace.parseToIR(cs, xref, res); michael@0: if (IR instanceof AlternateCS) { michael@0: return IR; michael@0: } michael@0: return ColorSpace.fromIR(IR); michael@0: }; michael@0: michael@0: ColorSpace.fromIR = function ColorSpace_fromIR(IR) { michael@0: var name = isArray(IR) ? IR[0] : IR; michael@0: var whitePoint, blackPoint; michael@0: michael@0: switch (name) { michael@0: case 'DeviceGrayCS': michael@0: return this.singletons.gray; michael@0: case 'DeviceRgbCS': michael@0: return this.singletons.rgb; michael@0: case 'DeviceCmykCS': michael@0: return this.singletons.cmyk; michael@0: case 'CalGrayCS': michael@0: whitePoint = IR[1].WhitePoint; michael@0: blackPoint = IR[1].BlackPoint; michael@0: var gamma = IR[1].Gamma; michael@0: return new CalGrayCS(whitePoint, blackPoint, gamma); michael@0: case 'PatternCS': michael@0: var basePatternCS = IR[1]; michael@0: if (basePatternCS) { michael@0: basePatternCS = ColorSpace.fromIR(basePatternCS); michael@0: } michael@0: return new PatternCS(basePatternCS); michael@0: case 'IndexedCS': michael@0: var baseIndexedCS = IR[1]; michael@0: var hiVal = IR[2]; michael@0: var lookup = IR[3]; michael@0: return new IndexedCS(ColorSpace.fromIR(baseIndexedCS), hiVal, lookup); michael@0: case 'AlternateCS': michael@0: var numComps = IR[1]; michael@0: var alt = IR[2]; michael@0: var tintFnIR = IR[3]; michael@0: michael@0: return new AlternateCS(numComps, ColorSpace.fromIR(alt), michael@0: PDFFunction.fromIR(tintFnIR)); michael@0: case 'LabCS': michael@0: whitePoint = IR[1].WhitePoint; michael@0: blackPoint = IR[1].BlackPoint; michael@0: var range = IR[1].Range; michael@0: return new LabCS(whitePoint, blackPoint, range); michael@0: default: michael@0: error('Unkown name ' + name); michael@0: } michael@0: return null; michael@0: }; michael@0: michael@0: ColorSpace.parseToIR = function ColorSpace_parseToIR(cs, xref, res) { michael@0: if (isName(cs)) { michael@0: var colorSpaces = res.get('ColorSpace'); michael@0: if (isDict(colorSpaces)) { michael@0: var refcs = colorSpaces.get(cs.name); michael@0: if (refcs) { michael@0: cs = refcs; michael@0: } michael@0: } michael@0: } michael@0: michael@0: cs = xref.fetchIfRef(cs); michael@0: var mode; michael@0: michael@0: if (isName(cs)) { michael@0: mode = cs.name; michael@0: this.mode = mode; michael@0: michael@0: switch (mode) { michael@0: case 'DeviceGray': michael@0: case 'G': michael@0: return 'DeviceGrayCS'; michael@0: case 'DeviceRGB': michael@0: case 'RGB': michael@0: return 'DeviceRgbCS'; michael@0: case 'DeviceCMYK': michael@0: case 'CMYK': michael@0: return 'DeviceCmykCS'; michael@0: case 'Pattern': michael@0: return ['PatternCS', null]; michael@0: default: michael@0: error('unrecognized colorspace ' + mode); michael@0: } michael@0: } else if (isArray(cs)) { michael@0: mode = cs[0].name; michael@0: this.mode = mode; michael@0: var numComps, params; michael@0: michael@0: switch (mode) { michael@0: case 'DeviceGray': michael@0: case 'G': michael@0: return 'DeviceGrayCS'; michael@0: case 'DeviceRGB': michael@0: case 'RGB': michael@0: return 'DeviceRgbCS'; michael@0: case 'DeviceCMYK': michael@0: case 'CMYK': michael@0: return 'DeviceCmykCS'; michael@0: case 'CalGray': michael@0: params = cs[1].getAll(); michael@0: return ['CalGrayCS', params]; michael@0: case 'CalRGB': michael@0: return 'DeviceRgbCS'; michael@0: case 'ICCBased': michael@0: var stream = xref.fetchIfRef(cs[1]); michael@0: var dict = stream.dict; michael@0: numComps = dict.get('N'); michael@0: if (numComps == 1) { michael@0: return 'DeviceGrayCS'; michael@0: } else if (numComps == 3) { michael@0: return 'DeviceRgbCS'; michael@0: } else if (numComps == 4) { michael@0: return 'DeviceCmykCS'; michael@0: } michael@0: break; michael@0: case 'Pattern': michael@0: var basePatternCS = cs[1]; michael@0: if (basePatternCS) { michael@0: basePatternCS = ColorSpace.parseToIR(basePatternCS, xref, res); michael@0: } michael@0: return ['PatternCS', basePatternCS]; michael@0: case 'Indexed': michael@0: case 'I': michael@0: var baseIndexedCS = ColorSpace.parseToIR(cs[1], xref, res); michael@0: var hiVal = cs[2] + 1; michael@0: var lookup = xref.fetchIfRef(cs[3]); michael@0: if (isStream(lookup)) { michael@0: lookup = lookup.getBytes(); michael@0: } michael@0: return ['IndexedCS', baseIndexedCS, hiVal, lookup]; michael@0: case 'Separation': michael@0: case 'DeviceN': michael@0: var name = cs[1]; michael@0: numComps = 1; michael@0: if (isName(name)) { michael@0: numComps = 1; michael@0: } else if (isArray(name)) { michael@0: numComps = name.length; michael@0: } michael@0: var alt = ColorSpace.parseToIR(cs[2], xref, res); michael@0: var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3])); michael@0: return ['AlternateCS', numComps, alt, tintFnIR]; michael@0: case 'Lab': michael@0: params = cs[1].getAll(); michael@0: return ['LabCS', params]; michael@0: default: michael@0: error('unimplemented color space object "' + mode + '"'); michael@0: } michael@0: } else { michael@0: error('unrecognized color space object: "' + cs + '"'); michael@0: } michael@0: return null; michael@0: }; michael@0: /** michael@0: * Checks if a decode map matches the default decode map for a color space. michael@0: * This handles the general decode maps where there are two values per michael@0: * component. e.g. [0, 1, 0, 1, 0, 1] for a RGB color. michael@0: * This does not handle Lab, Indexed, or Pattern decode maps since they are michael@0: * slightly different. michael@0: * @param {Array} decode Decode map (usually from an image). michael@0: * @param {Number} n Number of components the color space has. michael@0: */ michael@0: ColorSpace.isDefaultDecode = function ColorSpace_isDefaultDecode(decode, n) { michael@0: if (!decode) { michael@0: return true; michael@0: } michael@0: michael@0: if (n * 2 !== decode.length) { michael@0: warn('The decode map is not the correct length'); michael@0: return true; michael@0: } michael@0: for (var i = 0, ii = decode.length; i < ii; i += 2) { michael@0: if (decode[i] !== 0 || decode[i + 1] != 1) { michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: }; michael@0: michael@0: ColorSpace.singletons = { michael@0: get gray() { michael@0: return shadow(this, 'gray', new DeviceGrayCS()); michael@0: }, michael@0: get rgb() { michael@0: return shadow(this, 'rgb', new DeviceRgbCS()); michael@0: }, michael@0: get cmyk() { michael@0: return shadow(this, 'cmyk', new DeviceCmykCS()); michael@0: } michael@0: }; michael@0: michael@0: return ColorSpace; michael@0: })(); michael@0: michael@0: /** michael@0: * Alternate color space handles both Separation and DeviceN color spaces. A michael@0: * Separation color space is actually just a DeviceN with one color component. michael@0: * Both color spaces use a tinting function to convert colors to a base color michael@0: * space. michael@0: */ michael@0: var AlternateCS = (function AlternateCSClosure() { michael@0: function AlternateCS(numComps, base, tintFn) { michael@0: this.name = 'Alternate'; michael@0: this.numComps = numComps; michael@0: this.defaultColor = new Float32Array(numComps); michael@0: for (var i = 0; i < numComps; ++i) { michael@0: this.defaultColor[i] = 1; michael@0: } michael@0: this.base = base; michael@0: this.tintFn = tintFn; michael@0: } michael@0: michael@0: AlternateCS.prototype = { michael@0: getRgb: ColorSpace.prototype.getRgb, michael@0: getRgbItem: function AlternateCS_getRgbItem(src, srcOffset, michael@0: dest, destOffset) { michael@0: var baseNumComps = this.base.numComps; michael@0: var input = 'subarray' in src ? michael@0: src.subarray(srcOffset, srcOffset + this.numComps) : michael@0: Array.prototype.slice.call(src, srcOffset, srcOffset + this.numComps); michael@0: var tinted = this.tintFn(input); michael@0: this.base.getRgbItem(tinted, 0, dest, destOffset); michael@0: }, michael@0: getRgbBuffer: function AlternateCS_getRgbBuffer(src, srcOffset, count, michael@0: dest, destOffset, bits, michael@0: alpha01) { michael@0: var tintFn = this.tintFn; michael@0: var base = this.base; michael@0: var scale = 1 / ((1 << bits) - 1); michael@0: var baseNumComps = base.numComps; michael@0: var usesZeroToOneRange = base.usesZeroToOneRange; michael@0: var isPassthrough = (base.isPassthrough(8) || !usesZeroToOneRange) && michael@0: alpha01 === 0; michael@0: var pos = isPassthrough ? destOffset : 0; michael@0: var baseBuf = isPassthrough ? dest : new Uint8Array(baseNumComps * count); michael@0: var numComps = this.numComps; michael@0: michael@0: var scaled = new Float32Array(numComps); michael@0: var i, j; michael@0: for (i = 0; i < count; i++) { michael@0: for (j = 0; j < numComps; j++) { michael@0: scaled[j] = src[srcOffset++] * scale; michael@0: } michael@0: var tinted = tintFn(scaled); michael@0: if (usesZeroToOneRange) { michael@0: for (j = 0; j < baseNumComps; j++) { michael@0: baseBuf[pos++] = tinted[j] * 255; michael@0: } michael@0: } else { michael@0: base.getRgbItem(tinted, 0, baseBuf, pos); michael@0: pos += baseNumComps; michael@0: } michael@0: } michael@0: if (!isPassthrough) { michael@0: base.getRgbBuffer(baseBuf, 0, count, dest, destOffset, 8, alpha01); michael@0: } michael@0: }, michael@0: getOutputLength: function AlternateCS_getOutputLength(inputLength, michael@0: alpha01) { michael@0: return this.base.getOutputLength(inputLength * michael@0: this.base.numComps / this.numComps, michael@0: alpha01); michael@0: }, michael@0: isPassthrough: ColorSpace.prototype.isPassthrough, michael@0: fillRgb: ColorSpace.prototype.fillRgb, michael@0: isDefaultDecode: function AlternateCS_isDefaultDecode(decodeMap) { michael@0: return ColorSpace.isDefaultDecode(decodeMap, this.numComps); michael@0: }, michael@0: usesZeroToOneRange: true michael@0: }; michael@0: michael@0: return AlternateCS; michael@0: })(); michael@0: michael@0: var PatternCS = (function PatternCSClosure() { michael@0: function PatternCS(baseCS) { michael@0: this.name = 'Pattern'; michael@0: this.base = baseCS; michael@0: } michael@0: PatternCS.prototype = {}; michael@0: michael@0: return PatternCS; michael@0: })(); michael@0: michael@0: var IndexedCS = (function IndexedCSClosure() { michael@0: function IndexedCS(base, highVal, lookup) { michael@0: this.name = 'Indexed'; michael@0: this.numComps = 1; michael@0: this.defaultColor = new Uint8Array([0]); michael@0: this.base = base; michael@0: this.highVal = highVal; michael@0: michael@0: var baseNumComps = base.numComps; michael@0: var length = baseNumComps * highVal; michael@0: var lookupArray; michael@0: michael@0: if (isStream(lookup)) { michael@0: lookupArray = new Uint8Array(length); michael@0: var bytes = lookup.getBytes(length); michael@0: lookupArray.set(bytes); michael@0: } else if (isString(lookup)) { michael@0: lookupArray = new Uint8Array(length); michael@0: for (var i = 0; i < length; ++i) { michael@0: lookupArray[i] = lookup.charCodeAt(i); michael@0: } michael@0: } else if (lookup instanceof Uint8Array || lookup instanceof Array) { michael@0: lookupArray = lookup; michael@0: } else { michael@0: error('Unrecognized lookup table: ' + lookup); michael@0: } michael@0: this.lookup = lookupArray; michael@0: } michael@0: michael@0: IndexedCS.prototype = { michael@0: getRgb: ColorSpace.prototype.getRgb, michael@0: getRgbItem: function IndexedCS_getRgbItem(src, srcOffset, michael@0: dest, destOffset) { michael@0: var numComps = this.base.numComps; michael@0: var start = src[srcOffset] * numComps; michael@0: this.base.getRgbItem(this.lookup, start, dest, destOffset); michael@0: }, michael@0: getRgbBuffer: function IndexedCS_getRgbBuffer(src, srcOffset, count, michael@0: dest, destOffset, bits, michael@0: alpha01) { michael@0: var base = this.base; michael@0: var numComps = base.numComps; michael@0: var outputDelta = base.getOutputLength(numComps, alpha01); michael@0: var lookup = this.lookup; michael@0: michael@0: for (var i = 0; i < count; ++i) { michael@0: var lookupPos = src[srcOffset++] * numComps; michael@0: base.getRgbBuffer(lookup, lookupPos, 1, dest, destOffset, 8, alpha01); michael@0: destOffset += outputDelta; michael@0: } michael@0: }, michael@0: getOutputLength: function IndexedCS_getOutputLength(inputLength, alpha01) { michael@0: return this.base.getOutputLength(inputLength * this.base.numComps, michael@0: alpha01); michael@0: }, michael@0: isPassthrough: ColorSpace.prototype.isPassthrough, michael@0: fillRgb: ColorSpace.prototype.fillRgb, michael@0: isDefaultDecode: function IndexedCS_isDefaultDecode(decodeMap) { michael@0: // indexed color maps shouldn't be changed michael@0: return true; michael@0: }, michael@0: usesZeroToOneRange: true michael@0: }; michael@0: return IndexedCS; michael@0: })(); michael@0: michael@0: var DeviceGrayCS = (function DeviceGrayCSClosure() { michael@0: function DeviceGrayCS() { michael@0: this.name = 'DeviceGray'; michael@0: this.numComps = 1; michael@0: this.defaultColor = new Float32Array([0]); michael@0: } michael@0: michael@0: DeviceGrayCS.prototype = { michael@0: getRgb: ColorSpace.prototype.getRgb, michael@0: getRgbItem: function DeviceGrayCS_getRgbItem(src, srcOffset, michael@0: dest, destOffset) { michael@0: var c = (src[srcOffset] * 255) | 0; michael@0: c = c < 0 ? 0 : c > 255 ? 255 : c; michael@0: dest[destOffset] = dest[destOffset + 1] = dest[destOffset + 2] = c; michael@0: }, michael@0: getRgbBuffer: function DeviceGrayCS_getRgbBuffer(src, srcOffset, count, michael@0: dest, destOffset, bits, michael@0: alpha01) { michael@0: var scale = 255 / ((1 << bits) - 1); michael@0: var j = srcOffset, q = destOffset; michael@0: for (var i = 0; i < count; ++i) { michael@0: var c = (scale * src[j++]) | 0; michael@0: dest[q++] = c; michael@0: dest[q++] = c; michael@0: dest[q++] = c; michael@0: q += alpha01; michael@0: } michael@0: }, michael@0: getOutputLength: function DeviceGrayCS_getOutputLength(inputLength, michael@0: alpha01) { michael@0: return inputLength * (3 + alpha01); michael@0: }, michael@0: isPassthrough: ColorSpace.prototype.isPassthrough, michael@0: fillRgb: ColorSpace.prototype.fillRgb, michael@0: isDefaultDecode: function DeviceGrayCS_isDefaultDecode(decodeMap) { michael@0: return ColorSpace.isDefaultDecode(decodeMap, this.numComps); michael@0: }, michael@0: usesZeroToOneRange: true michael@0: }; michael@0: return DeviceGrayCS; michael@0: })(); michael@0: michael@0: var DeviceRgbCS = (function DeviceRgbCSClosure() { michael@0: function DeviceRgbCS() { michael@0: this.name = 'DeviceRGB'; michael@0: this.numComps = 3; michael@0: this.defaultColor = new Float32Array([0, 0, 0]); michael@0: } michael@0: DeviceRgbCS.prototype = { michael@0: getRgb: ColorSpace.prototype.getRgb, michael@0: getRgbItem: function DeviceRgbCS_getRgbItem(src, srcOffset, michael@0: dest, destOffset) { michael@0: var r = (src[srcOffset] * 255) | 0; michael@0: var g = (src[srcOffset + 1] * 255) | 0; michael@0: var b = (src[srcOffset + 2] * 255) | 0; michael@0: dest[destOffset] = r < 0 ? 0 : r > 255 ? 255 : r; michael@0: dest[destOffset + 1] = g < 0 ? 0 : g > 255 ? 255 : g; michael@0: dest[destOffset + 2] = b < 0 ? 0 : b > 255 ? 255 : b; michael@0: }, michael@0: getRgbBuffer: function DeviceRgbCS_getRgbBuffer(src, srcOffset, count, michael@0: dest, destOffset, bits, michael@0: alpha01) { michael@0: if (bits === 8 && alpha01 === 0) { michael@0: dest.set(src.subarray(srcOffset, srcOffset + count * 3), destOffset); michael@0: return; michael@0: } michael@0: var scale = 255 / ((1 << bits) - 1); michael@0: var j = srcOffset, q = destOffset; michael@0: for (var i = 0; i < count; ++i) { michael@0: dest[q++] = (scale * src[j++]) | 0; michael@0: dest[q++] = (scale * src[j++]) | 0; michael@0: dest[q++] = (scale * src[j++]) | 0; michael@0: q += alpha01; michael@0: } michael@0: }, michael@0: getOutputLength: function DeviceRgbCS_getOutputLength(inputLength, michael@0: alpha01) { michael@0: return (inputLength * (3 + alpha01) / 3) | 0; michael@0: }, michael@0: isPassthrough: function DeviceRgbCS_isPassthrough(bits) { michael@0: return bits == 8; michael@0: }, michael@0: fillRgb: ColorSpace.prototype.fillRgb, michael@0: isDefaultDecode: function DeviceRgbCS_isDefaultDecode(decodeMap) { michael@0: return ColorSpace.isDefaultDecode(decodeMap, this.numComps); michael@0: }, michael@0: usesZeroToOneRange: true michael@0: }; michael@0: return DeviceRgbCS; michael@0: })(); michael@0: michael@0: var DeviceCmykCS = (function DeviceCmykCSClosure() { michael@0: // The coefficients below was found using numerical analysis: the method of michael@0: // steepest descent for the sum((f_i - color_value_i)^2) for r/g/b colors, michael@0: // where color_value is the tabular value from the table of sampled RGB colors michael@0: // from CMYK US Web Coated (SWOP) colorspace, and f_i is the corresponding michael@0: // CMYK color conversion using the estimation below: michael@0: // f(A, B,.. N) = Acc+Bcm+Ccy+Dck+c+Fmm+Gmy+Hmk+Im+Jyy+Kyk+Ly+Mkk+Nk+255 michael@0: function convertToRgb(src, srcOffset, srcScale, dest, destOffset) { michael@0: var c = src[srcOffset + 0] * srcScale; michael@0: var m = src[srcOffset + 1] * srcScale; michael@0: var y = src[srcOffset + 2] * srcScale; michael@0: var k = src[srcOffset + 3] * srcScale; michael@0: michael@0: var r = michael@0: (c * (-4.387332384609988 * c + 54.48615194189176 * m + michael@0: 18.82290502165302 * y + 212.25662451639585 * k + michael@0: -285.2331026137004) + michael@0: m * (1.7149763477362134 * m - 5.6096736904047315 * y + michael@0: -17.873870861415444 * k - 5.497006427196366) + michael@0: y * (-2.5217340131683033 * y - 21.248923337353073 * k + michael@0: 17.5119270841813) + michael@0: k * (-21.86122147463605 * k - 189.48180835922747) + 255) | 0; michael@0: var g = michael@0: (c * (8.841041422036149 * c + 60.118027045597366 * m + michael@0: 6.871425592049007 * y + 31.159100130055922 * k + michael@0: -79.2970844816548) + michael@0: m * (-15.310361306967817 * m + 17.575251261109482 * y + michael@0: 131.35250912493976 * k - 190.9453302588951) + michael@0: y * (4.444339102852739 * y + 9.8632861493405 * k - 24.86741582555878) + michael@0: k * (-20.737325471181034 * k - 187.80453709719578) + 255) | 0; michael@0: var b = michael@0: (c * (0.8842522430003296 * c + 8.078677503112928 * m + michael@0: 30.89978309703729 * y - 0.23883238689178934 * k + michael@0: -14.183576799673286) + michael@0: m * (10.49593273432072 * m + 63.02378494754052 * y + michael@0: 50.606957656360734 * k - 112.23884253719248) + michael@0: y * (0.03296041114873217 * y + 115.60384449646641 * k + michael@0: -193.58209356861505) + michael@0: k * (-22.33816807309886 * k - 180.12613974708367) + 255) | 0; michael@0: michael@0: dest[destOffset] = r > 255 ? 255 : r < 0 ? 0 : r; michael@0: dest[destOffset + 1] = g > 255 ? 255 : g < 0 ? 0 : g; michael@0: dest[destOffset + 2] = b > 255 ? 255 : b < 0 ? 0 : b; michael@0: } michael@0: michael@0: function DeviceCmykCS() { michael@0: this.name = 'DeviceCMYK'; michael@0: this.numComps = 4; michael@0: this.defaultColor = new Float32Array([0, 0, 0, 1]); michael@0: } michael@0: DeviceCmykCS.prototype = { michael@0: getRgb: ColorSpace.prototype.getRgb, michael@0: getRgbItem: function DeviceCmykCS_getRgbItem(src, srcOffset, michael@0: dest, destOffset) { michael@0: convertToRgb(src, srcOffset, 1, dest, destOffset); michael@0: }, michael@0: getRgbBuffer: function DeviceCmykCS_getRgbBuffer(src, srcOffset, count, michael@0: dest, destOffset, bits, michael@0: alpha01) { michael@0: var scale = 1 / ((1 << bits) - 1); michael@0: for (var i = 0; i < count; i++) { michael@0: convertToRgb(src, srcOffset, scale, dest, destOffset); michael@0: srcOffset += 4; michael@0: destOffset += 3 + alpha01; michael@0: } michael@0: }, michael@0: getOutputLength: function DeviceCmykCS_getOutputLength(inputLength, michael@0: alpha01) { michael@0: return (inputLength / 4 * (3 + alpha01)) | 0; michael@0: }, michael@0: isPassthrough: ColorSpace.prototype.isPassthrough, michael@0: fillRgb: ColorSpace.prototype.fillRgb, michael@0: isDefaultDecode: function DeviceCmykCS_isDefaultDecode(decodeMap) { michael@0: return ColorSpace.isDefaultDecode(decodeMap, this.numComps); michael@0: }, michael@0: usesZeroToOneRange: true michael@0: }; michael@0: michael@0: return DeviceCmykCS; michael@0: })(); michael@0: michael@0: // michael@0: // CalGrayCS: Based on "PDF Reference, Sixth Ed", p.245 michael@0: // michael@0: var CalGrayCS = (function CalGrayCSClosure() { michael@0: function CalGrayCS(whitePoint, blackPoint, gamma) { michael@0: this.name = 'CalGray'; michael@0: this.numComps = 1; michael@0: this.defaultColor = new Float32Array([0]); michael@0: michael@0: if (!whitePoint) { michael@0: error('WhitePoint missing - required for color space CalGray'); michael@0: } michael@0: blackPoint = blackPoint || [0, 0, 0]; michael@0: gamma = gamma || 1; michael@0: michael@0: // Translate arguments to spec variables. michael@0: this.XW = whitePoint[0]; michael@0: this.YW = whitePoint[1]; michael@0: this.ZW = whitePoint[2]; michael@0: michael@0: this.XB = blackPoint[0]; michael@0: this.YB = blackPoint[1]; michael@0: this.ZB = blackPoint[2]; michael@0: michael@0: this.G = gamma; michael@0: michael@0: // Validate variables as per spec. michael@0: if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) { michael@0: error('Invalid WhitePoint components for ' + this.name + michael@0: ', no fallback available'); michael@0: } michael@0: michael@0: if (this.XB < 0 || this.YB < 0 || this.ZB < 0) { michael@0: info('Invalid BlackPoint for ' + this.name + ', falling back to default'); michael@0: this.XB = this.YB = this.ZB = 0; michael@0: } michael@0: michael@0: if (this.XB !== 0 || this.YB !== 0 || this.ZB !== 0) { michael@0: warn(this.name + ', BlackPoint: XB: ' + this.XB + ', YB: ' + this.YB + michael@0: ', ZB: ' + this.ZB + ', only default values are supported.'); michael@0: } michael@0: michael@0: if (this.G < 1) { michael@0: info('Invalid Gamma: ' + this.G + ' for ' + this.name + michael@0: ', falling back to default'); michael@0: this.G = 1; michael@0: } michael@0: } michael@0: michael@0: function convertToRgb(cs, src, srcOffset, dest, destOffset, scale) { michael@0: // A represents a gray component of a calibrated gray space. michael@0: // A <---> AG in the spec michael@0: var A = src[srcOffset] * scale; michael@0: var AG = Math.pow(A, cs.G); michael@0: michael@0: // Computes intermediate variables M, L, N as per spec. michael@0: // Except if other than default BlackPoint values are used. michael@0: var M = cs.XW * AG; michael@0: var L = cs.YW * AG; michael@0: var N = cs.ZW * AG; michael@0: michael@0: // Decode XYZ, as per spec. michael@0: var X = M; michael@0: var Y = L; michael@0: var Z = N; michael@0: michael@0: // http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html, Ch 4. michael@0: // This yields values in range [0, 100]. michael@0: var Lstar = Math.max(116 * Math.pow(Y, 1 / 3) - 16, 0); michael@0: michael@0: // Convert values to rgb range [0, 255]. michael@0: dest[destOffset] = Lstar * 255 / 100; michael@0: dest[destOffset + 1] = Lstar * 255 / 100; michael@0: dest[destOffset + 2] = Lstar * 255 / 100; michael@0: } michael@0: michael@0: CalGrayCS.prototype = { michael@0: getRgb: ColorSpace.prototype.getRgb, michael@0: getRgbItem: function CalGrayCS_getRgbItem(src, srcOffset, michael@0: dest, destOffset) { michael@0: convertToRgb(this, src, srcOffset, dest, destOffset, 1); michael@0: }, michael@0: getRgbBuffer: function CalGrayCS_getRgbBuffer(src, srcOffset, count, michael@0: dest, destOffset, bits, michael@0: alpha01) { michael@0: var scale = 1 / ((1 << bits) - 1); michael@0: michael@0: for (var i = 0; i < count; ++i) { michael@0: convertToRgb(this, src, srcOffset, dest, destOffset, scale); michael@0: srcOffset += 1; michael@0: destOffset += 3 + alpha01; michael@0: } michael@0: }, michael@0: getOutputLength: function CalGrayCS_getOutputLength(inputLength, alpha01) { michael@0: return inputLength * (3 + alpha01); michael@0: }, michael@0: isPassthrough: ColorSpace.prototype.isPassthrough, michael@0: fillRgb: ColorSpace.prototype.fillRgb, michael@0: isDefaultDecode: function CalGrayCS_isDefaultDecode(decodeMap) { michael@0: return ColorSpace.isDefaultDecode(decodeMap, this.numComps); michael@0: }, michael@0: usesZeroToOneRange: true michael@0: }; michael@0: return CalGrayCS; michael@0: })(); michael@0: michael@0: // michael@0: // LabCS: Based on "PDF Reference, Sixth Ed", p.250 michael@0: // michael@0: var LabCS = (function LabCSClosure() { michael@0: function LabCS(whitePoint, blackPoint, range) { michael@0: this.name = 'Lab'; michael@0: this.numComps = 3; michael@0: this.defaultColor = new Float32Array([0, 0, 0]); michael@0: michael@0: if (!whitePoint) { michael@0: error('WhitePoint missing - required for color space Lab'); michael@0: } michael@0: blackPoint = blackPoint || [0, 0, 0]; michael@0: range = range || [-100, 100, -100, 100]; michael@0: michael@0: // Translate args to spec variables michael@0: this.XW = whitePoint[0]; michael@0: this.YW = whitePoint[1]; michael@0: this.ZW = whitePoint[2]; michael@0: this.amin = range[0]; michael@0: this.amax = range[1]; michael@0: this.bmin = range[2]; michael@0: this.bmax = range[3]; michael@0: michael@0: // These are here just for completeness - the spec doesn't offer any michael@0: // formulas that use BlackPoint in Lab michael@0: this.XB = blackPoint[0]; michael@0: this.YB = blackPoint[1]; michael@0: this.ZB = blackPoint[2]; michael@0: michael@0: // Validate vars as per spec michael@0: if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) { michael@0: error('Invalid WhitePoint components, no fallback available'); michael@0: } michael@0: michael@0: if (this.XB < 0 || this.YB < 0 || this.ZB < 0) { michael@0: info('Invalid BlackPoint, falling back to default'); michael@0: this.XB = this.YB = this.ZB = 0; michael@0: } michael@0: michael@0: if (this.amin > this.amax || this.bmin > this.bmax) { michael@0: info('Invalid Range, falling back to defaults'); michael@0: this.amin = -100; michael@0: this.amax = 100; michael@0: this.bmin = -100; michael@0: this.bmax = 100; michael@0: } michael@0: } michael@0: michael@0: // Function g(x) from spec michael@0: function fn_g(x) { michael@0: if (x >= 6 / 29) { michael@0: return x * x * x; michael@0: } else { michael@0: return (108 / 841) * (x - 4 / 29); michael@0: } michael@0: } michael@0: michael@0: function decode(value, high1, low2, high2) { michael@0: return low2 + (value) * (high2 - low2) / (high1); michael@0: } michael@0: michael@0: // If decoding is needed maxVal should be 2^bits per component - 1. michael@0: function convertToRgb(cs, src, srcOffset, maxVal, dest, destOffset) { michael@0: // XXX: Lab input is in the range of [0, 100], [amin, amax], [bmin, bmax] michael@0: // not the usual [0, 1]. If a command like setFillColor is used the src michael@0: // values will already be within the correct range. However, if we are michael@0: // converting an image we have to map the values to the correct range given michael@0: // above. michael@0: // Ls,as,bs <---> L*,a*,b* in the spec michael@0: var Ls = src[srcOffset]; michael@0: var as = src[srcOffset + 1]; michael@0: var bs = src[srcOffset + 2]; michael@0: if (maxVal !== false) { michael@0: Ls = decode(Ls, maxVal, 0, 100); michael@0: as = decode(as, maxVal, cs.amin, cs.amax); michael@0: bs = decode(bs, maxVal, cs.bmin, cs.bmax); michael@0: } michael@0: michael@0: // Adjust limits of 'as' and 'bs' michael@0: as = as > cs.amax ? cs.amax : as < cs.amin ? cs.amin : as; michael@0: bs = bs > cs.bmax ? cs.bmax : bs < cs.bmin ? cs.bmin : bs; michael@0: michael@0: // Computes intermediate variables X,Y,Z as per spec michael@0: var M = (Ls + 16) / 116; michael@0: var L = M + (as / 500); michael@0: var N = M - (bs / 200); michael@0: michael@0: var X = cs.XW * fn_g(L); michael@0: var Y = cs.YW * fn_g(M); michael@0: var Z = cs.ZW * fn_g(N); michael@0: michael@0: var r, g, b; michael@0: // Using different conversions for D50 and D65 white points, michael@0: // per http://www.color.org/srgb.pdf michael@0: if (cs.ZW < 1) { michael@0: // Assuming D50 (X=0.9642, Y=1.00, Z=0.8249) michael@0: r = X * 3.1339 + Y * -1.6170 + Z * -0.4906; michael@0: g = X * -0.9785 + Y * 1.9160 + Z * 0.0333; michael@0: b = X * 0.0720 + Y * -0.2290 + Z * 1.4057; michael@0: } else { michael@0: // Assuming D65 (X=0.9505, Y=1.00, Z=1.0888) michael@0: r = X * 3.2406 + Y * -1.5372 + Z * -0.4986; michael@0: g = X * -0.9689 + Y * 1.8758 + Z * 0.0415; michael@0: b = X * 0.0557 + Y * -0.2040 + Z * 1.0570; michael@0: } michael@0: // clamp color values to [0,1] range then convert to [0,255] range. michael@0: dest[destOffset] = r <= 0 ? 0 : r >= 1 ? 255 : Math.sqrt(r) * 255 | 0; michael@0: dest[destOffset + 1] = g <= 0 ? 0 : g >= 1 ? 255 : Math.sqrt(g) * 255 | 0; michael@0: dest[destOffset + 2] = b <= 0 ? 0 : b >= 1 ? 255 : Math.sqrt(b) * 255 | 0; michael@0: } michael@0: michael@0: LabCS.prototype = { michael@0: getRgb: ColorSpace.prototype.getRgb, michael@0: getRgbItem: function LabCS_getRgbItem(src, srcOffset, dest, destOffset) { michael@0: convertToRgb(this, src, srcOffset, false, dest, destOffset); michael@0: }, michael@0: getRgbBuffer: function LabCS_getRgbBuffer(src, srcOffset, count, michael@0: dest, destOffset, bits, michael@0: alpha01) { michael@0: var maxVal = (1 << bits) - 1; michael@0: for (var i = 0; i < count; i++) { michael@0: convertToRgb(this, src, srcOffset, maxVal, dest, destOffset); michael@0: srcOffset += 3; michael@0: destOffset += 3 + alpha01; michael@0: } michael@0: }, michael@0: getOutputLength: function LabCS_getOutputLength(inputLength, alpha01) { michael@0: return (inputLength * (3 + alpha01) / 3) | 0; michael@0: }, michael@0: isPassthrough: ColorSpace.prototype.isPassthrough, michael@0: isDefaultDecode: function LabCS_isDefaultDecode(decodeMap) { michael@0: // XXX: Decoding is handled with the lab conversion because of the strange michael@0: // ranges that are used. michael@0: return true; michael@0: }, michael@0: usesZeroToOneRange: false michael@0: }; michael@0: return LabCS; michael@0: })(); michael@0: michael@0: michael@0: michael@0: var PDFFunction = (function PDFFunctionClosure() { michael@0: var CONSTRUCT_SAMPLED = 0; michael@0: var CONSTRUCT_INTERPOLATED = 2; michael@0: var CONSTRUCT_STICHED = 3; michael@0: var CONSTRUCT_POSTSCRIPT = 4; michael@0: michael@0: return { michael@0: getSampleArray: function PDFFunction_getSampleArray(size, outputSize, bps, michael@0: str) { michael@0: var i, ii; michael@0: var length = 1; michael@0: for (i = 0, ii = size.length; i < ii; i++) { michael@0: length *= size[i]; michael@0: } michael@0: length *= outputSize; michael@0: michael@0: var array = []; michael@0: var codeSize = 0; michael@0: var codeBuf = 0; michael@0: // 32 is a valid bps so shifting won't work michael@0: var sampleMul = 1.0 / (Math.pow(2.0, bps) - 1); michael@0: michael@0: var strBytes = str.getBytes((length * bps + 7) / 8); michael@0: var strIdx = 0; michael@0: for (i = 0; i < length; i++) { michael@0: while (codeSize < bps) { michael@0: codeBuf <<= 8; michael@0: codeBuf |= strBytes[strIdx++]; michael@0: codeSize += 8; michael@0: } michael@0: codeSize -= bps; michael@0: array.push((codeBuf >> codeSize) * sampleMul); michael@0: codeBuf &= (1 << codeSize) - 1; michael@0: } michael@0: return array; michael@0: }, michael@0: michael@0: getIR: function PDFFunction_getIR(xref, fn) { michael@0: var dict = fn.dict; michael@0: if (!dict) { michael@0: dict = fn; michael@0: } michael@0: michael@0: var types = [this.constructSampled, michael@0: null, michael@0: this.constructInterpolated, michael@0: this.constructStiched, michael@0: this.constructPostScript]; michael@0: michael@0: var typeNum = dict.get('FunctionType'); michael@0: var typeFn = types[typeNum]; michael@0: if (!typeFn) { michael@0: error('Unknown type of function'); michael@0: } michael@0: michael@0: return typeFn.call(this, fn, dict, xref); michael@0: }, michael@0: michael@0: fromIR: function PDFFunction_fromIR(IR) { michael@0: var type = IR[0]; michael@0: switch (type) { michael@0: case CONSTRUCT_SAMPLED: michael@0: return this.constructSampledFromIR(IR); michael@0: case CONSTRUCT_INTERPOLATED: michael@0: return this.constructInterpolatedFromIR(IR); michael@0: case CONSTRUCT_STICHED: michael@0: return this.constructStichedFromIR(IR); michael@0: //case CONSTRUCT_POSTSCRIPT: michael@0: default: michael@0: return this.constructPostScriptFromIR(IR); michael@0: } michael@0: }, michael@0: michael@0: parse: function PDFFunction_parse(xref, fn) { michael@0: var IR = this.getIR(xref, fn); michael@0: return this.fromIR(IR); michael@0: }, michael@0: michael@0: constructSampled: function PDFFunction_constructSampled(str, dict) { michael@0: function toMultiArray(arr) { michael@0: var inputLength = arr.length; michael@0: var out = []; michael@0: var index = 0; michael@0: for (var i = 0; i < inputLength; i += 2) { michael@0: out[index] = [arr[i], arr[i + 1]]; michael@0: ++index; michael@0: } michael@0: return out; michael@0: } michael@0: var domain = dict.get('Domain'); michael@0: var range = dict.get('Range'); michael@0: michael@0: if (!domain || !range) { michael@0: error('No domain or range'); michael@0: } michael@0: michael@0: var inputSize = domain.length / 2; michael@0: var outputSize = range.length / 2; michael@0: michael@0: domain = toMultiArray(domain); michael@0: range = toMultiArray(range); michael@0: michael@0: var size = dict.get('Size'); michael@0: var bps = dict.get('BitsPerSample'); michael@0: var order = dict.get('Order') || 1; michael@0: if (order !== 1) { michael@0: // No description how cubic spline interpolation works in PDF32000:2008 michael@0: // As in poppler, ignoring order, linear interpolation may work as good michael@0: info('No support for cubic spline interpolation: ' + order); michael@0: } michael@0: michael@0: var encode = dict.get('Encode'); michael@0: if (!encode) { michael@0: encode = []; michael@0: for (var i = 0; i < inputSize; ++i) { michael@0: encode.push(0); michael@0: encode.push(size[i] - 1); michael@0: } michael@0: } michael@0: encode = toMultiArray(encode); michael@0: michael@0: var decode = dict.get('Decode'); michael@0: if (!decode) { michael@0: decode = range; michael@0: } else { michael@0: decode = toMultiArray(decode); michael@0: } michael@0: michael@0: var samples = this.getSampleArray(size, outputSize, bps, str); michael@0: michael@0: return [ michael@0: CONSTRUCT_SAMPLED, inputSize, domain, encode, decode, samples, size, michael@0: outputSize, Math.pow(2, bps) - 1, range michael@0: ]; michael@0: }, michael@0: michael@0: constructSampledFromIR: function PDFFunction_constructSampledFromIR(IR) { michael@0: // See chapter 3, page 109 of the PDF reference michael@0: function interpolate(x, xmin, xmax, ymin, ymax) { michael@0: return ymin + ((x - xmin) * ((ymax - ymin) / (xmax - xmin))); michael@0: } michael@0: michael@0: return function constructSampledFromIRResult(args) { michael@0: // See chapter 3, page 110 of the PDF reference. michael@0: var m = IR[1]; michael@0: var domain = IR[2]; michael@0: var encode = IR[3]; michael@0: var decode = IR[4]; michael@0: var samples = IR[5]; michael@0: var size = IR[6]; michael@0: var n = IR[7]; michael@0: //var mask = IR[8]; michael@0: var range = IR[9]; michael@0: michael@0: if (m != args.length) { michael@0: error('Incorrect number of arguments: ' + m + ' != ' + michael@0: args.length); michael@0: } michael@0: michael@0: var x = args; michael@0: michael@0: // Building the cube vertices: its part and sample index michael@0: // http://rjwagner49.com/Mathematics/Interpolation.pdf michael@0: var cubeVertices = 1 << m; michael@0: var cubeN = new Float64Array(cubeVertices); michael@0: var cubeVertex = new Uint32Array(cubeVertices); michael@0: var i, j; michael@0: for (j = 0; j < cubeVertices; j++) { michael@0: cubeN[j] = 1; michael@0: } michael@0: michael@0: var k = n, pos = 1; michael@0: // Map x_i to y_j for 0 <= i < m using the sampled function. michael@0: for (i = 0; i < m; ++i) { michael@0: // x_i' = min(max(x_i, Domain_2i), Domain_2i+1) michael@0: var domain_2i = domain[i][0]; michael@0: var domain_2i_1 = domain[i][1]; michael@0: var xi = Math.min(Math.max(x[i], domain_2i), domain_2i_1); michael@0: michael@0: // e_i = Interpolate(x_i', Domain_2i, Domain_2i+1, michael@0: // Encode_2i, Encode_2i+1) michael@0: var e = interpolate(xi, domain_2i, domain_2i_1, michael@0: encode[i][0], encode[i][1]); michael@0: michael@0: // e_i' = min(max(e_i, 0), Size_i - 1) michael@0: var size_i = size[i]; michael@0: e = Math.min(Math.max(e, 0), size_i - 1); michael@0: michael@0: // Adjusting the cube: N and vertex sample index michael@0: var e0 = e < size_i - 1 ? Math.floor(e) : e - 1; // e1 = e0 + 1; michael@0: var n0 = e0 + 1 - e; // (e1 - e) / (e1 - e0); michael@0: var n1 = e - e0; // (e - e0) / (e1 - e0); michael@0: var offset0 = e0 * k; michael@0: var offset1 = offset0 + k; // e1 * k michael@0: for (j = 0; j < cubeVertices; j++) { michael@0: if (j & pos) { michael@0: cubeN[j] *= n1; michael@0: cubeVertex[j] += offset1; michael@0: } else { michael@0: cubeN[j] *= n0; michael@0: cubeVertex[j] += offset0; michael@0: } michael@0: } michael@0: michael@0: k *= size_i; michael@0: pos <<= 1; michael@0: } michael@0: michael@0: var y = new Float64Array(n); michael@0: for (j = 0; j < n; ++j) { michael@0: // Sum all cube vertices' samples portions michael@0: var rj = 0; michael@0: for (i = 0; i < cubeVertices; i++) { michael@0: rj += samples[cubeVertex[i] + j] * cubeN[i]; michael@0: } michael@0: michael@0: // r_j' = Interpolate(r_j, 0, 2^BitsPerSample - 1, michael@0: // Decode_2j, Decode_2j+1) michael@0: rj = interpolate(rj, 0, 1, decode[j][0], decode[j][1]); michael@0: michael@0: // y_j = min(max(r_j, range_2j), range_2j+1) michael@0: y[j] = Math.min(Math.max(rj, range[j][0]), range[j][1]); michael@0: } michael@0: michael@0: return y; michael@0: }; michael@0: }, michael@0: michael@0: constructInterpolated: function PDFFunction_constructInterpolated(str, michael@0: dict) { michael@0: var c0 = dict.get('C0') || [0]; michael@0: var c1 = dict.get('C1') || [1]; michael@0: var n = dict.get('N'); michael@0: michael@0: if (!isArray(c0) || !isArray(c1)) { michael@0: error('Illegal dictionary for interpolated function'); michael@0: } michael@0: michael@0: var length = c0.length; michael@0: var diff = []; michael@0: for (var i = 0; i < length; ++i) { michael@0: diff.push(c1[i] - c0[i]); michael@0: } michael@0: michael@0: return [CONSTRUCT_INTERPOLATED, c0, diff, n]; michael@0: }, michael@0: michael@0: constructInterpolatedFromIR: michael@0: function PDFFunction_constructInterpolatedFromIR(IR) { michael@0: var c0 = IR[1]; michael@0: var diff = IR[2]; michael@0: var n = IR[3]; michael@0: michael@0: var length = diff.length; michael@0: michael@0: return function constructInterpolatedFromIRResult(args) { michael@0: var x = n == 1 ? args[0] : Math.pow(args[0], n); michael@0: michael@0: var out = []; michael@0: for (var j = 0; j < length; ++j) { michael@0: out.push(c0[j] + (x * diff[j])); michael@0: } michael@0: michael@0: return out; michael@0: michael@0: }; michael@0: }, michael@0: michael@0: constructStiched: function PDFFunction_constructStiched(fn, dict, xref) { michael@0: var domain = dict.get('Domain'); michael@0: michael@0: if (!domain) { michael@0: error('No domain'); michael@0: } michael@0: michael@0: var inputSize = domain.length / 2; michael@0: if (inputSize != 1) { michael@0: error('Bad domain for stiched function'); michael@0: } michael@0: michael@0: var fnRefs = dict.get('Functions'); michael@0: var fns = []; michael@0: for (var i = 0, ii = fnRefs.length; i < ii; ++i) { michael@0: fns.push(PDFFunction.getIR(xref, xref.fetchIfRef(fnRefs[i]))); michael@0: } michael@0: michael@0: var bounds = dict.get('Bounds'); michael@0: var encode = dict.get('Encode'); michael@0: michael@0: return [CONSTRUCT_STICHED, domain, bounds, encode, fns]; michael@0: }, michael@0: michael@0: constructStichedFromIR: function PDFFunction_constructStichedFromIR(IR) { michael@0: var domain = IR[1]; michael@0: var bounds = IR[2]; michael@0: var encode = IR[3]; michael@0: var fnsIR = IR[4]; michael@0: var fns = []; michael@0: michael@0: for (var i = 0, ii = fnsIR.length; i < ii; i++) { michael@0: fns.push(PDFFunction.fromIR(fnsIR[i])); michael@0: } michael@0: michael@0: return function constructStichedFromIRResult(args) { michael@0: var clip = function constructStichedFromIRClip(v, min, max) { michael@0: if (v > max) { michael@0: v = max; michael@0: } else if (v < min) { michael@0: v = min; michael@0: } michael@0: return v; michael@0: }; michael@0: michael@0: // clip to domain michael@0: var v = clip(args[0], domain[0], domain[1]); michael@0: // calulate which bound the value is in michael@0: for (var i = 0, ii = bounds.length; i < ii; ++i) { michael@0: if (v < bounds[i]) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // encode value into domain of function michael@0: var dmin = domain[0]; michael@0: if (i > 0) { michael@0: dmin = bounds[i - 1]; michael@0: } michael@0: var dmax = domain[1]; michael@0: if (i < bounds.length) { michael@0: dmax = bounds[i]; michael@0: } michael@0: michael@0: var rmin = encode[2 * i]; michael@0: var rmax = encode[2 * i + 1]; michael@0: michael@0: var v2 = rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin); michael@0: michael@0: // call the appropriate function michael@0: return fns[i]([v2]); michael@0: }; michael@0: }, michael@0: michael@0: constructPostScript: function PDFFunction_constructPostScript(fn, dict, michael@0: xref) { michael@0: var domain = dict.get('Domain'); michael@0: var range = dict.get('Range'); michael@0: michael@0: if (!domain) { michael@0: error('No domain.'); michael@0: } michael@0: michael@0: if (!range) { michael@0: error('No range.'); michael@0: } michael@0: michael@0: var lexer = new PostScriptLexer(fn); michael@0: var parser = new PostScriptParser(lexer); michael@0: var code = parser.parse(); michael@0: michael@0: return [CONSTRUCT_POSTSCRIPT, domain, range, code]; michael@0: }, michael@0: michael@0: constructPostScriptFromIR: function PDFFunction_constructPostScriptFromIR( michael@0: IR) { michael@0: var domain = IR[1]; michael@0: var range = IR[2]; michael@0: var code = IR[3]; michael@0: var numOutputs = range.length / 2; michael@0: var evaluator = new PostScriptEvaluator(code); michael@0: // Cache the values for a big speed up, the cache size is limited though michael@0: // since the number of possible values can be huge from a PS function. michael@0: var cache = new FunctionCache(); michael@0: return function constructPostScriptFromIRResult(args) { michael@0: var initialStack = []; michael@0: for (var i = 0, ii = (domain.length / 2); i < ii; ++i) { michael@0: initialStack.push(args[i]); michael@0: } michael@0: michael@0: var key = initialStack.join('_'); michael@0: if (cache.has(key)) { michael@0: return cache.get(key); michael@0: } michael@0: michael@0: var stack = evaluator.execute(initialStack); michael@0: var transformed = []; michael@0: for (i = numOutputs - 1; i >= 0; --i) { michael@0: var out = stack.pop(); michael@0: var rangeIndex = 2 * i; michael@0: if (out < range[rangeIndex]) { michael@0: out = range[rangeIndex]; michael@0: } else if (out > range[rangeIndex + 1]) { michael@0: out = range[rangeIndex + 1]; michael@0: } michael@0: transformed[i] = out; michael@0: } michael@0: cache.set(key, transformed); michael@0: return transformed; michael@0: }; michael@0: } michael@0: }; michael@0: })(); michael@0: michael@0: var FunctionCache = (function FunctionCacheClosure() { michael@0: // Of 10 PDF's with type4 functions the maxium number of distinct values seen michael@0: // was 256. This still may need some tweaking in the future though. michael@0: var MAX_CACHE_SIZE = 1024; michael@0: function FunctionCache() { michael@0: this.cache = {}; michael@0: this.total = 0; michael@0: } michael@0: FunctionCache.prototype = { michael@0: has: function FunctionCache_has(key) { michael@0: return key in this.cache; michael@0: }, michael@0: get: function FunctionCache_get(key) { michael@0: return this.cache[key]; michael@0: }, michael@0: set: function FunctionCache_set(key, value) { michael@0: if (this.total < MAX_CACHE_SIZE) { michael@0: this.cache[key] = value; michael@0: this.total++; michael@0: } michael@0: } michael@0: }; michael@0: return FunctionCache; michael@0: })(); michael@0: michael@0: var PostScriptStack = (function PostScriptStackClosure() { michael@0: var MAX_STACK_SIZE = 100; michael@0: function PostScriptStack(initialStack) { michael@0: this.stack = initialStack || []; michael@0: } michael@0: michael@0: PostScriptStack.prototype = { michael@0: push: function PostScriptStack_push(value) { michael@0: if (this.stack.length >= MAX_STACK_SIZE) { michael@0: error('PostScript function stack overflow.'); michael@0: } michael@0: this.stack.push(value); michael@0: }, michael@0: pop: function PostScriptStack_pop() { michael@0: if (this.stack.length <= 0) { michael@0: error('PostScript function stack underflow.'); michael@0: } michael@0: return this.stack.pop(); michael@0: }, michael@0: copy: function PostScriptStack_copy(n) { michael@0: if (this.stack.length + n >= MAX_STACK_SIZE) { michael@0: error('PostScript function stack overflow.'); michael@0: } michael@0: var stack = this.stack; michael@0: for (var i = stack.length - n, j = n - 1; j >= 0; j--, i++) { michael@0: stack.push(stack[i]); michael@0: } michael@0: }, michael@0: index: function PostScriptStack_index(n) { michael@0: this.push(this.stack[this.stack.length - n - 1]); michael@0: }, michael@0: // rotate the last n stack elements p times michael@0: roll: function PostScriptStack_roll(n, p) { michael@0: var stack = this.stack; michael@0: var l = stack.length - n; michael@0: var r = stack.length - 1, c = l + (p - Math.floor(p / n) * n), i, j, t; michael@0: for (i = l, j = r; i < j; i++, j--) { michael@0: t = stack[i]; stack[i] = stack[j]; stack[j] = t; michael@0: } michael@0: for (i = l, j = c - 1; i < j; i++, j--) { michael@0: t = stack[i]; stack[i] = stack[j]; stack[j] = t; michael@0: } michael@0: for (i = c, j = r; i < j; i++, j--) { michael@0: t = stack[i]; stack[i] = stack[j]; stack[j] = t; michael@0: } michael@0: } michael@0: }; michael@0: return PostScriptStack; michael@0: })(); michael@0: var PostScriptEvaluator = (function PostScriptEvaluatorClosure() { michael@0: function PostScriptEvaluator(operators) { michael@0: this.operators = operators; michael@0: } michael@0: PostScriptEvaluator.prototype = { michael@0: execute: function PostScriptEvaluator_execute(initialStack) { michael@0: var stack = new PostScriptStack(initialStack); michael@0: var counter = 0; michael@0: var operators = this.operators; michael@0: var length = operators.length; michael@0: var operator, a, b; michael@0: while (counter < length) { michael@0: operator = operators[counter++]; michael@0: if (typeof operator == 'number') { michael@0: // Operator is really an operand and should be pushed to the stack. michael@0: stack.push(operator); michael@0: continue; michael@0: } michael@0: switch (operator) { michael@0: // non standard ps operators michael@0: case 'jz': // jump if false michael@0: b = stack.pop(); michael@0: a = stack.pop(); michael@0: if (!a) { michael@0: counter = b; michael@0: } michael@0: break; michael@0: case 'j': // jump michael@0: a = stack.pop(); michael@0: counter = a; michael@0: break; michael@0: michael@0: // all ps operators in alphabetical order (excluding if/ifelse) michael@0: case 'abs': michael@0: a = stack.pop(); michael@0: stack.push(Math.abs(a)); michael@0: break; michael@0: case 'add': michael@0: b = stack.pop(); michael@0: a = stack.pop(); michael@0: stack.push(a + b); michael@0: break; michael@0: case 'and': michael@0: b = stack.pop(); michael@0: a = stack.pop(); michael@0: if (isBool(a) && isBool(b)) { michael@0: stack.push(a && b); michael@0: } else { michael@0: stack.push(a & b); michael@0: } michael@0: break; michael@0: case 'atan': michael@0: a = stack.pop(); michael@0: stack.push(Math.atan(a)); michael@0: break; michael@0: case 'bitshift': michael@0: b = stack.pop(); michael@0: a = stack.pop(); michael@0: if (a > 0) { michael@0: stack.push(a << b); michael@0: } else { michael@0: stack.push(a >> b); michael@0: } michael@0: break; michael@0: case 'ceiling': michael@0: a = stack.pop(); michael@0: stack.push(Math.ceil(a)); michael@0: break; michael@0: case 'copy': michael@0: a = stack.pop(); michael@0: stack.copy(a); michael@0: break; michael@0: case 'cos': michael@0: a = stack.pop(); michael@0: stack.push(Math.cos(a)); michael@0: break; michael@0: case 'cvi': michael@0: a = stack.pop() | 0; michael@0: stack.push(a); michael@0: break; michael@0: case 'cvr': michael@0: // noop michael@0: break; michael@0: case 'div': michael@0: b = stack.pop(); michael@0: a = stack.pop(); michael@0: stack.push(a / b); michael@0: break; michael@0: case 'dup': michael@0: stack.copy(1); michael@0: break; michael@0: case 'eq': michael@0: b = stack.pop(); michael@0: a = stack.pop(); michael@0: stack.push(a == b); michael@0: break; michael@0: case 'exch': michael@0: stack.roll(2, 1); michael@0: break; michael@0: case 'exp': michael@0: b = stack.pop(); michael@0: a = stack.pop(); michael@0: stack.push(Math.pow(a, b)); michael@0: break; michael@0: case 'false': michael@0: stack.push(false); michael@0: break; michael@0: case 'floor': michael@0: a = stack.pop(); michael@0: stack.push(Math.floor(a)); michael@0: break; michael@0: case 'ge': michael@0: b = stack.pop(); michael@0: a = stack.pop(); michael@0: stack.push(a >= b); michael@0: break; michael@0: case 'gt': michael@0: b = stack.pop(); michael@0: a = stack.pop(); michael@0: stack.push(a > b); michael@0: break; michael@0: case 'idiv': michael@0: b = stack.pop(); michael@0: a = stack.pop(); michael@0: stack.push((a / b) | 0); michael@0: break; michael@0: case 'index': michael@0: a = stack.pop(); michael@0: stack.index(a); michael@0: break; michael@0: case 'le': michael@0: b = stack.pop(); michael@0: a = stack.pop(); michael@0: stack.push(a <= b); michael@0: break; michael@0: case 'ln': michael@0: a = stack.pop(); michael@0: stack.push(Math.log(a)); michael@0: break; michael@0: case 'log': michael@0: a = stack.pop(); michael@0: stack.push(Math.log(a) / Math.LN10); michael@0: break; michael@0: case 'lt': michael@0: b = stack.pop(); michael@0: a = stack.pop(); michael@0: stack.push(a < b); michael@0: break; michael@0: case 'mod': michael@0: b = stack.pop(); michael@0: a = stack.pop(); michael@0: stack.push(a % b); michael@0: break; michael@0: case 'mul': michael@0: b = stack.pop(); michael@0: a = stack.pop(); michael@0: stack.push(a * b); michael@0: break; michael@0: case 'ne': michael@0: b = stack.pop(); michael@0: a = stack.pop(); michael@0: stack.push(a != b); michael@0: break; michael@0: case 'neg': michael@0: a = stack.pop(); michael@0: stack.push(-b); michael@0: break; michael@0: case 'not': michael@0: a = stack.pop(); michael@0: if (isBool(a) && isBool(b)) { michael@0: stack.push(a && b); michael@0: } else { michael@0: stack.push(a & b); michael@0: } michael@0: break; michael@0: case 'or': michael@0: b = stack.pop(); michael@0: a = stack.pop(); michael@0: if (isBool(a) && isBool(b)) { michael@0: stack.push(a || b); michael@0: } else { michael@0: stack.push(a | b); michael@0: } michael@0: break; michael@0: case 'pop': michael@0: stack.pop(); michael@0: break; michael@0: case 'roll': michael@0: b = stack.pop(); michael@0: a = stack.pop(); michael@0: stack.roll(a, b); michael@0: break; michael@0: case 'round': michael@0: a = stack.pop(); michael@0: stack.push(Math.round(a)); michael@0: break; michael@0: case 'sin': michael@0: a = stack.pop(); michael@0: stack.push(Math.sin(a)); michael@0: break; michael@0: case 'sqrt': michael@0: a = stack.pop(); michael@0: stack.push(Math.sqrt(a)); michael@0: break; michael@0: case 'sub': michael@0: b = stack.pop(); michael@0: a = stack.pop(); michael@0: stack.push(a - b); michael@0: break; michael@0: case 'true': michael@0: stack.push(true); michael@0: break; michael@0: case 'truncate': michael@0: a = stack.pop(); michael@0: a = a < 0 ? Math.ceil(a) : Math.floor(a); michael@0: stack.push(a); michael@0: break; michael@0: case 'xor': michael@0: b = stack.pop(); michael@0: a = stack.pop(); michael@0: if (isBool(a) && isBool(b)) { michael@0: stack.push(a != b); michael@0: } else { michael@0: stack.push(a ^ b); michael@0: } michael@0: break; michael@0: default: michael@0: error('Unknown operator ' + operator); michael@0: break; michael@0: } michael@0: } michael@0: return stack.stack; michael@0: } michael@0: }; michael@0: return PostScriptEvaluator; michael@0: })(); michael@0: michael@0: michael@0: var HIGHLIGHT_OFFSET = 4; // px michael@0: var SUPPORTED_TYPES = ['Link', 'Text', 'Widget']; michael@0: michael@0: var Annotation = (function AnnotationClosure() { michael@0: // 12.5.5: Algorithm: Appearance streams michael@0: function getTransformMatrix(rect, bbox, matrix) { michael@0: var bounds = Util.getAxialAlignedBoundingBox(bbox, matrix); michael@0: var minX = bounds[0]; michael@0: var minY = bounds[1]; michael@0: var maxX = bounds[2]; michael@0: var maxY = bounds[3]; michael@0: michael@0: if (minX === maxX || minY === maxY) { michael@0: // From real-life file, bbox was [0, 0, 0, 0]. In this case, michael@0: // just apply the transform for rect michael@0: return [1, 0, 0, 1, rect[0], rect[1]]; michael@0: } michael@0: michael@0: var xRatio = (rect[2] - rect[0]) / (maxX - minX); michael@0: var yRatio = (rect[3] - rect[1]) / (maxY - minY); michael@0: return [ michael@0: xRatio, michael@0: 0, michael@0: 0, michael@0: yRatio, michael@0: rect[0] - minX * xRatio, michael@0: rect[1] - minY * yRatio michael@0: ]; michael@0: } michael@0: michael@0: function getDefaultAppearance(dict) { michael@0: var appearanceState = dict.get('AP'); michael@0: if (!isDict(appearanceState)) { michael@0: return; michael@0: } michael@0: michael@0: var appearance; michael@0: var appearances = appearanceState.get('N'); michael@0: if (isDict(appearances)) { michael@0: var as = dict.get('AS'); michael@0: if (as && appearances.has(as.name)) { michael@0: appearance = appearances.get(as.name); michael@0: } michael@0: } else { michael@0: appearance = appearances; michael@0: } michael@0: return appearance; michael@0: } michael@0: michael@0: function Annotation(params) { michael@0: if (params.data) { michael@0: this.data = params.data; michael@0: return; michael@0: } michael@0: michael@0: var dict = params.dict; michael@0: var data = this.data = {}; michael@0: michael@0: data.subtype = dict.get('Subtype').name; michael@0: var rect = dict.get('Rect') || [0, 0, 0, 0]; michael@0: data.rect = Util.normalizeRect(rect); michael@0: data.annotationFlags = dict.get('F'); michael@0: michael@0: var color = dict.get('C'); michael@0: if (isArray(color) && color.length === 3) { michael@0: // TODO(mack): currently only supporting rgb; need support different michael@0: // colorspaces michael@0: data.color = color; michael@0: } else { michael@0: data.color = [0, 0, 0]; michael@0: } michael@0: michael@0: // Some types of annotations have border style dict which has more michael@0: // info than the border array michael@0: if (dict.has('BS')) { michael@0: var borderStyle = dict.get('BS'); michael@0: data.borderWidth = borderStyle.has('W') ? borderStyle.get('W') : 1; michael@0: } else { michael@0: var borderArray = dict.get('Border') || [0, 0, 1]; michael@0: data.borderWidth = borderArray[2] || 0; michael@0: michael@0: // TODO: implement proper support for annotations with line dash patterns. michael@0: var dashArray = borderArray[3]; michael@0: if (data.borderWidth > 0 && dashArray && isArray(dashArray)) { michael@0: var dashArrayLength = dashArray.length; michael@0: if (dashArrayLength > 0) { michael@0: // According to the PDF specification: the elements in a dashArray michael@0: // shall be numbers that are nonnegative and not all equal to zero. michael@0: var isInvalid = false; michael@0: var numPositive = 0; michael@0: for (var i = 0; i < dashArrayLength; i++) { michael@0: var validNumber = (+dashArray[i] >= 0); michael@0: if (!validNumber) { michael@0: isInvalid = true; michael@0: break; michael@0: } else if (dashArray[i] > 0) { michael@0: numPositive++; michael@0: } michael@0: } michael@0: if (isInvalid || numPositive === 0) { michael@0: data.borderWidth = 0; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: this.appearance = getDefaultAppearance(dict); michael@0: data.hasAppearance = !!this.appearance; michael@0: data.id = params.ref.num; michael@0: } michael@0: michael@0: Annotation.prototype = { michael@0: michael@0: getData: function Annotation_getData() { michael@0: return this.data; michael@0: }, michael@0: michael@0: hasHtml: function Annotation_hasHtml() { michael@0: return false; michael@0: }, michael@0: michael@0: getHtmlElement: function Annotation_getHtmlElement(commonObjs) { michael@0: throw new NotImplementedException( michael@0: 'getHtmlElement() should be implemented in subclass'); michael@0: }, michael@0: michael@0: // TODO(mack): Remove this, it's not really that helpful. michael@0: getEmptyContainer: function Annotation_getEmptyContainer(tagName, rect, michael@0: borderWidth) { michael@0: assert(!isWorker, michael@0: 'getEmptyContainer() should be called from main thread'); michael@0: michael@0: var bWidth = borderWidth || 0; michael@0: michael@0: rect = rect || this.data.rect; michael@0: var element = document.createElement(tagName); michael@0: element.style.borderWidth = bWidth + 'px'; michael@0: var width = rect[2] - rect[0] - 2 * bWidth; michael@0: var height = rect[3] - rect[1] - 2 * bWidth; michael@0: element.style.width = width + 'px'; michael@0: element.style.height = height + 'px'; michael@0: return element; michael@0: }, michael@0: michael@0: isInvisible: function Annotation_isInvisible() { michael@0: var data = this.data; michael@0: if (data && SUPPORTED_TYPES.indexOf(data.subtype) !== -1) { michael@0: return false; michael@0: } else { michael@0: return !!(data && michael@0: data.annotationFlags && // Default: not invisible michael@0: data.annotationFlags & 0x1); // Invisible michael@0: } michael@0: }, michael@0: michael@0: isViewable: function Annotation_isViewable() { michael@0: var data = this.data; michael@0: return !!(!this.isInvisible() && michael@0: data && michael@0: (!data.annotationFlags || michael@0: !(data.annotationFlags & 0x22)) && // Hidden or NoView michael@0: data.rect); // rectangle is nessessary michael@0: }, michael@0: michael@0: isPrintable: function Annotation_isPrintable() { michael@0: var data = this.data; michael@0: return !!(!this.isInvisible() && michael@0: data && michael@0: data.annotationFlags && // Default: not printable michael@0: data.annotationFlags & 0x4 && // Print michael@0: data.rect); // rectangle is nessessary michael@0: }, michael@0: michael@0: loadResources: function(keys) { michael@0: var promise = new LegacyPromise(); michael@0: this.appearance.dict.getAsync('Resources').then(function(resources) { michael@0: if (!resources) { michael@0: promise.resolve(); michael@0: return; michael@0: } michael@0: var objectLoader = new ObjectLoader(resources.map, michael@0: keys, michael@0: resources.xref); michael@0: objectLoader.load().then(function() { michael@0: promise.resolve(resources); michael@0: }); michael@0: }.bind(this)); michael@0: michael@0: return promise; michael@0: }, michael@0: michael@0: getOperatorList: function Annotation_getOperatorList(evaluator) { michael@0: michael@0: var promise = new LegacyPromise(); michael@0: michael@0: if (!this.appearance) { michael@0: promise.resolve(new OperatorList()); michael@0: return promise; michael@0: } michael@0: michael@0: var data = this.data; michael@0: michael@0: var appearanceDict = this.appearance.dict; michael@0: var resourcesPromise = this.loadResources([ michael@0: 'ExtGState', michael@0: 'ColorSpace', michael@0: 'Pattern', michael@0: 'Shading', michael@0: 'XObject', michael@0: 'Font' michael@0: // ProcSet michael@0: // Properties michael@0: ]); michael@0: var bbox = appearanceDict.get('BBox') || [0, 0, 1, 1]; michael@0: var matrix = appearanceDict.get('Matrix') || [1, 0, 0, 1, 0 ,0]; michael@0: var transform = getTransformMatrix(data.rect, bbox, matrix); michael@0: michael@0: resourcesPromise.then(function(resources) { michael@0: var opList = new OperatorList(); michael@0: opList.addOp(OPS.beginAnnotation, [data.rect, transform, matrix]); michael@0: evaluator.getOperatorList(this.appearance, resources, opList); michael@0: opList.addOp(OPS.endAnnotation, []); michael@0: promise.resolve(opList); michael@0: michael@0: this.appearance.reset(); michael@0: }.bind(this)); michael@0: michael@0: return promise; michael@0: } michael@0: }; michael@0: michael@0: Annotation.getConstructor = michael@0: function Annotation_getConstructor(subtype, fieldType) { michael@0: michael@0: if (!subtype) { michael@0: return; michael@0: } michael@0: michael@0: // TODO(mack): Implement FreeText annotations michael@0: if (subtype === 'Link') { michael@0: return LinkAnnotation; michael@0: } else if (subtype === 'Text') { michael@0: return TextAnnotation; michael@0: } else if (subtype === 'Widget') { michael@0: if (!fieldType) { michael@0: return; michael@0: } michael@0: michael@0: if (fieldType === 'Tx') { michael@0: return TextWidgetAnnotation; michael@0: } else { michael@0: return WidgetAnnotation; michael@0: } michael@0: } else { michael@0: return Annotation; michael@0: } michael@0: }; michael@0: michael@0: // TODO(mack): Support loading annotation from data michael@0: Annotation.fromData = function Annotation_fromData(data) { michael@0: var subtype = data.subtype; michael@0: var fieldType = data.fieldType; michael@0: var Constructor = Annotation.getConstructor(subtype, fieldType); michael@0: if (Constructor) { michael@0: return new Constructor({ data: data }); michael@0: } michael@0: }; michael@0: michael@0: Annotation.fromRef = function Annotation_fromRef(xref, ref) { michael@0: michael@0: var dict = xref.fetchIfRef(ref); michael@0: if (!isDict(dict)) { michael@0: return; michael@0: } michael@0: michael@0: var subtype = dict.get('Subtype'); michael@0: subtype = isName(subtype) ? subtype.name : ''; michael@0: if (!subtype) { michael@0: return; michael@0: } michael@0: michael@0: var fieldType = Util.getInheritableProperty(dict, 'FT'); michael@0: fieldType = isName(fieldType) ? fieldType.name : ''; michael@0: michael@0: var Constructor = Annotation.getConstructor(subtype, fieldType); michael@0: if (!Constructor) { michael@0: return; michael@0: } michael@0: michael@0: var params = { michael@0: dict: dict, michael@0: ref: ref, michael@0: }; michael@0: michael@0: var annotation = new Constructor(params); michael@0: michael@0: if (annotation.isViewable() || annotation.isPrintable()) { michael@0: return annotation; michael@0: } else { michael@0: warn('unimplemented annotation type: ' + subtype); michael@0: } michael@0: }; michael@0: michael@0: Annotation.appendToOperatorList = function Annotation_appendToOperatorList( michael@0: annotations, opList, pdfManager, partialEvaluator, intent) { michael@0: michael@0: function reject(e) { michael@0: annotationsReadyPromise.reject(e); michael@0: } michael@0: michael@0: var annotationsReadyPromise = new LegacyPromise(); michael@0: michael@0: var annotationPromises = []; michael@0: for (var i = 0, n = annotations.length; i < n; ++i) { michael@0: if (intent === 'display' && annotations[i].isViewable() || michael@0: intent === 'print' && annotations[i].isPrintable()) { michael@0: annotationPromises.push( michael@0: annotations[i].getOperatorList(partialEvaluator)); michael@0: } michael@0: } michael@0: Promise.all(annotationPromises).then(function(datas) { michael@0: opList.addOp(OPS.beginAnnotations, []); michael@0: for (var i = 0, n = datas.length; i < n; ++i) { michael@0: var annotOpList = datas[i]; michael@0: opList.addOpList(annotOpList); michael@0: } michael@0: opList.addOp(OPS.endAnnotations, []); michael@0: annotationsReadyPromise.resolve(); michael@0: }, reject); michael@0: michael@0: return annotationsReadyPromise; michael@0: }; michael@0: michael@0: return Annotation; michael@0: })(); michael@0: PDFJS.Annotation = Annotation; michael@0: michael@0: michael@0: var WidgetAnnotation = (function WidgetAnnotationClosure() { michael@0: michael@0: function WidgetAnnotation(params) { michael@0: Annotation.call(this, params); michael@0: michael@0: if (params.data) { michael@0: return; michael@0: } michael@0: michael@0: var dict = params.dict; michael@0: var data = this.data; michael@0: michael@0: data.fieldValue = stringToPDFString( michael@0: Util.getInheritableProperty(dict, 'V') || ''); michael@0: data.alternativeText = stringToPDFString(dict.get('TU') || ''); michael@0: data.defaultAppearance = Util.getInheritableProperty(dict, 'DA') || ''; michael@0: var fieldType = Util.getInheritableProperty(dict, 'FT'); michael@0: data.fieldType = isName(fieldType) ? fieldType.name : ''; michael@0: data.fieldFlags = Util.getInheritableProperty(dict, 'Ff') || 0; michael@0: this.fieldResources = Util.getInheritableProperty(dict, 'DR') || Dict.empty; michael@0: michael@0: // Building the full field name by collecting the field and michael@0: // its ancestors 'T' data and joining them using '.'. michael@0: var fieldName = []; michael@0: var namedItem = dict; michael@0: var ref = params.ref; michael@0: while (namedItem) { michael@0: var parent = namedItem.get('Parent'); michael@0: var parentRef = namedItem.getRaw('Parent'); michael@0: var name = namedItem.get('T'); michael@0: if (name) { michael@0: fieldName.unshift(stringToPDFString(name)); michael@0: } else { michael@0: // The field name is absent, that means more than one field michael@0: // with the same name may exist. Replacing the empty name michael@0: // with the '`' plus index in the parent's 'Kids' array. michael@0: // This is not in the PDF spec but necessary to id the michael@0: // the input controls. michael@0: var kids = parent.get('Kids'); michael@0: var j, jj; michael@0: for (j = 0, jj = kids.length; j < jj; j++) { michael@0: var kidRef = kids[j]; michael@0: if (kidRef.num == ref.num && kidRef.gen == ref.gen) { michael@0: break; michael@0: } michael@0: } michael@0: fieldName.unshift('`' + j); michael@0: } michael@0: namedItem = parent; michael@0: ref = parentRef; michael@0: } michael@0: data.fullName = fieldName.join('.'); michael@0: } michael@0: michael@0: var parent = Annotation.prototype; michael@0: Util.inherit(WidgetAnnotation, Annotation, { michael@0: isViewable: function WidgetAnnotation_isViewable() { michael@0: if (this.data.fieldType === 'Sig') { michael@0: warn('unimplemented annotation type: Widget signature'); michael@0: return false; michael@0: } michael@0: michael@0: return parent.isViewable.call(this); michael@0: } michael@0: }); michael@0: michael@0: return WidgetAnnotation; michael@0: })(); michael@0: michael@0: var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() { michael@0: function TextWidgetAnnotation(params) { michael@0: WidgetAnnotation.call(this, params); michael@0: michael@0: if (params.data) { michael@0: return; michael@0: } michael@0: michael@0: this.data.textAlignment = Util.getInheritableProperty(params.dict, 'Q'); michael@0: } michael@0: michael@0: // TODO(mack): This dupes some of the logic in CanvasGraphics.setFont() michael@0: function setTextStyles(element, item, fontObj) { michael@0: michael@0: var style = element.style; michael@0: style.fontSize = item.fontSize + 'px'; michael@0: style.direction = item.fontDirection < 0 ? 'rtl': 'ltr'; michael@0: michael@0: if (!fontObj) { michael@0: return; michael@0: } michael@0: michael@0: style.fontWeight = fontObj.black ? michael@0: (fontObj.bold ? 'bolder' : 'bold') : michael@0: (fontObj.bold ? 'bold' : 'normal'); michael@0: style.fontStyle = fontObj.italic ? 'italic' : 'normal'; michael@0: michael@0: var fontName = fontObj.loadedName; michael@0: var fontFamily = fontName ? '"' + fontName + '", ' : ''; michael@0: // Use a reasonable default font if the font doesn't specify a fallback michael@0: var fallbackName = fontObj.fallbackName || 'Helvetica, sans-serif'; michael@0: style.fontFamily = fontFamily + fallbackName; michael@0: } michael@0: michael@0: michael@0: Util.inherit(TextWidgetAnnotation, WidgetAnnotation, { michael@0: hasHtml: function TextWidgetAnnotation_hasHtml() { michael@0: return !this.data.hasAppearance && !!this.data.fieldValue; michael@0: }, michael@0: michael@0: getHtmlElement: function TextWidgetAnnotation_getHtmlElement(commonObjs) { michael@0: assert(!isWorker, 'getHtmlElement() shall be called from main thread'); michael@0: michael@0: var item = this.data; michael@0: michael@0: var element = this.getEmptyContainer('div'); michael@0: element.style.display = 'table'; michael@0: michael@0: var content = document.createElement('div'); michael@0: content.textContent = item.fieldValue; michael@0: var textAlignment = item.textAlignment; michael@0: content.style.textAlign = ['left', 'center', 'right'][textAlignment]; michael@0: content.style.verticalAlign = 'middle'; michael@0: content.style.display = 'table-cell'; michael@0: michael@0: var fontObj = item.fontRefName ? michael@0: commonObjs.getData(item.fontRefName) : null; michael@0: setTextStyles(content, item, fontObj); michael@0: michael@0: element.appendChild(content); michael@0: michael@0: return element; michael@0: }, michael@0: michael@0: getOperatorList: function TextWidgetAnnotation_getOperatorList(evaluator) { michael@0: if (this.appearance) { michael@0: return Annotation.prototype.getOperatorList.call(this, evaluator); michael@0: } michael@0: michael@0: var promise = new LegacyPromise(); michael@0: var opList = new OperatorList(); michael@0: var data = this.data; michael@0: michael@0: // Even if there is an appearance stream, ignore it. This is the michael@0: // behaviour used by Adobe Reader. michael@0: michael@0: var defaultAppearance = data.defaultAppearance; michael@0: if (!defaultAppearance) { michael@0: promise.resolve(opList); michael@0: return promise; michael@0: } michael@0: michael@0: // Include any font resources found in the default appearance michael@0: michael@0: var stream = new Stream(stringToBytes(defaultAppearance)); michael@0: evaluator.getOperatorList(stream, this.fieldResources, opList); michael@0: var appearanceFnArray = opList.fnArray; michael@0: var appearanceArgsArray = opList.argsArray; michael@0: var fnArray = []; michael@0: michael@0: // TODO(mack): Add support for stroke color michael@0: data.rgb = [0, 0, 0]; michael@0: // TODO THIS DOESN'T MAKE ANY SENSE SINCE THE fnArray IS EMPTY! michael@0: for (var i = 0, n = fnArray.length; i < n; ++i) { michael@0: var fnId = appearanceFnArray[i]; michael@0: var args = appearanceArgsArray[i]; michael@0: michael@0: if (fnId === OPS.setFont) { michael@0: data.fontRefName = args[0]; michael@0: var size = args[1]; michael@0: if (size < 0) { michael@0: data.fontDirection = -1; michael@0: data.fontSize = -size; michael@0: } else { michael@0: data.fontDirection = 1; michael@0: data.fontSize = size; michael@0: } michael@0: } else if (fnId === OPS.setFillRGBColor) { michael@0: data.rgb = args; michael@0: } else if (fnId === OPS.setFillGray) { michael@0: var rgbValue = args[0] * 255; michael@0: data.rgb = [rgbValue, rgbValue, rgbValue]; michael@0: } michael@0: } michael@0: promise.resolve(opList); michael@0: return promise; michael@0: } michael@0: }); michael@0: michael@0: return TextWidgetAnnotation; michael@0: })(); michael@0: michael@0: var InteractiveAnnotation = (function InteractiveAnnotationClosure() { michael@0: function InteractiveAnnotation(params) { michael@0: Annotation.call(this, params); michael@0: } michael@0: michael@0: Util.inherit(InteractiveAnnotation, Annotation, { michael@0: hasHtml: function InteractiveAnnotation_hasHtml() { michael@0: return true; michael@0: }, michael@0: michael@0: highlight: function InteractiveAnnotation_highlight() { michael@0: if (this.highlightElement && michael@0: this.highlightElement.hasAttribute('hidden')) { michael@0: this.highlightElement.removeAttribute('hidden'); michael@0: } michael@0: }, michael@0: michael@0: unhighlight: function InteractiveAnnotation_unhighlight() { michael@0: if (this.highlightElement && michael@0: !this.highlightElement.hasAttribute('hidden')) { michael@0: this.highlightElement.setAttribute('hidden', true); michael@0: } michael@0: }, michael@0: michael@0: initContainer: function InteractiveAnnotation_initContainer() { michael@0: michael@0: var item = this.data; michael@0: var rect = item.rect; michael@0: michael@0: var container = this.getEmptyContainer('section', rect, item.borderWidth); michael@0: container.style.backgroundColor = item.color; michael@0: michael@0: var color = item.color; michael@0: var rgb = []; michael@0: for (var i = 0; i < 3; ++i) { michael@0: rgb[i] = Math.round(color[i] * 255); michael@0: } michael@0: item.colorCssRgb = Util.makeCssRgb(rgb); michael@0: michael@0: var highlight = document.createElement('div'); michael@0: highlight.className = 'annotationHighlight'; michael@0: highlight.style.left = highlight.style.top = -HIGHLIGHT_OFFSET + 'px'; michael@0: highlight.style.right = highlight.style.bottom = -HIGHLIGHT_OFFSET + 'px'; michael@0: highlight.setAttribute('hidden', true); michael@0: michael@0: this.highlightElement = highlight; michael@0: container.appendChild(this.highlightElement); michael@0: michael@0: return container; michael@0: } michael@0: }); michael@0: michael@0: return InteractiveAnnotation; michael@0: })(); michael@0: michael@0: var TextAnnotation = (function TextAnnotationClosure() { michael@0: function TextAnnotation(params) { michael@0: InteractiveAnnotation.call(this, params); michael@0: michael@0: if (params.data) { michael@0: return; michael@0: } michael@0: michael@0: var dict = params.dict; michael@0: var data = this.data; michael@0: michael@0: var content = dict.get('Contents'); michael@0: var title = dict.get('T'); michael@0: data.content = stringToPDFString(content || ''); michael@0: data.title = stringToPDFString(title || ''); michael@0: michael@0: if (data.hasAppearance) { michael@0: data.name = 'NoIcon'; michael@0: } else { michael@0: data.name = dict.has('Name') ? dict.get('Name').name : 'Note'; michael@0: } michael@0: michael@0: if (dict.has('C')) { michael@0: data.hasBgColor = true; michael@0: } michael@0: } michael@0: michael@0: var ANNOT_MIN_SIZE = 10; michael@0: michael@0: Util.inherit(TextAnnotation, InteractiveAnnotation, { michael@0: michael@0: getHtmlElement: function TextAnnotation_getHtmlElement(commonObjs) { michael@0: assert(!isWorker, 'getHtmlElement() shall be called from main thread'); michael@0: michael@0: var item = this.data; michael@0: var rect = item.rect; michael@0: michael@0: // sanity check because of OOo-generated PDFs michael@0: if ((rect[3] - rect[1]) < ANNOT_MIN_SIZE) { michael@0: rect[3] = rect[1] + ANNOT_MIN_SIZE; michael@0: } michael@0: if ((rect[2] - rect[0]) < ANNOT_MIN_SIZE) { michael@0: rect[2] = rect[0] + (rect[3] - rect[1]); // make it square michael@0: } michael@0: michael@0: var container = this.initContainer(); michael@0: container.className = 'annotText'; michael@0: michael@0: var image = document.createElement('img'); michael@0: image.style.height = container.style.height; michael@0: image.style.width = container.style.width; michael@0: var iconName = item.name; michael@0: image.src = PDFJS.imageResourcesPath + 'annotation-' + michael@0: iconName.toLowerCase() + '.svg'; michael@0: image.alt = '[{{type}} Annotation]'; michael@0: image.dataset.l10nId = 'text_annotation_type'; michael@0: image.dataset.l10nArgs = JSON.stringify({type: iconName}); michael@0: michael@0: var contentWrapper = document.createElement('div'); michael@0: contentWrapper.className = 'annotTextContentWrapper'; michael@0: contentWrapper.style.left = Math.floor(rect[2] - rect[0] + 5) + 'px'; michael@0: contentWrapper.style.top = '-10px'; michael@0: michael@0: var content = document.createElement('div'); michael@0: content.className = 'annotTextContent'; michael@0: content.setAttribute('hidden', true); michael@0: michael@0: var i, ii; michael@0: if (item.hasBgColor) { michael@0: var color = item.color; michael@0: var rgb = []; michael@0: for (i = 0; i < 3; ++i) { michael@0: // Enlighten the color (70%) michael@0: var c = Math.round(color[i] * 255); michael@0: rgb[i] = Math.round((255 - c) * 0.7) + c; michael@0: } michael@0: content.style.backgroundColor = Util.makeCssRgb(rgb); michael@0: } michael@0: michael@0: var title = document.createElement('h1'); michael@0: var text = document.createElement('p'); michael@0: title.textContent = item.title; michael@0: michael@0: if (!item.content && !item.title) { michael@0: content.setAttribute('hidden', true); michael@0: } else { michael@0: var e = document.createElement('span'); michael@0: var lines = item.content.split(/(?:\r\n?|\n)/); michael@0: for (i = 0, ii = lines.length; i < ii; ++i) { michael@0: var line = lines[i]; michael@0: e.appendChild(document.createTextNode(line)); michael@0: if (i < (ii - 1)) { michael@0: e.appendChild(document.createElement('br')); michael@0: } michael@0: } michael@0: text.appendChild(e); michael@0: michael@0: var pinned = false; michael@0: michael@0: var showAnnotation = function showAnnotation(pin) { michael@0: if (pin) { michael@0: pinned = true; michael@0: } michael@0: if (content.hasAttribute('hidden')) { michael@0: container.style.zIndex += 1; michael@0: content.removeAttribute('hidden'); michael@0: } michael@0: }; michael@0: michael@0: var hideAnnotation = function hideAnnotation(unpin) { michael@0: if (unpin) { michael@0: pinned = false; michael@0: } michael@0: if (!content.hasAttribute('hidden') && !pinned) { michael@0: container.style.zIndex -= 1; michael@0: content.setAttribute('hidden', true); michael@0: } michael@0: }; michael@0: michael@0: var toggleAnnotation = function toggleAnnotation() { michael@0: if (pinned) { michael@0: hideAnnotation(true); michael@0: } else { michael@0: showAnnotation(true); michael@0: } michael@0: }; michael@0: michael@0: image.addEventListener('click', function image_clickHandler() { michael@0: toggleAnnotation(); michael@0: }, false); michael@0: image.addEventListener('mouseover', function image_mouseOverHandler() { michael@0: showAnnotation(); michael@0: }, false); michael@0: image.addEventListener('mouseout', function image_mouseOutHandler() { michael@0: hideAnnotation(); michael@0: }, false); michael@0: michael@0: content.addEventListener('click', function content_clickHandler() { michael@0: hideAnnotation(true); michael@0: }, false); michael@0: } michael@0: michael@0: content.appendChild(title); michael@0: content.appendChild(text); michael@0: contentWrapper.appendChild(content); michael@0: container.appendChild(image); michael@0: container.appendChild(contentWrapper); michael@0: michael@0: return container; michael@0: } michael@0: }); michael@0: michael@0: return TextAnnotation; michael@0: })(); michael@0: michael@0: var LinkAnnotation = (function LinkAnnotationClosure() { michael@0: function LinkAnnotation(params) { michael@0: InteractiveAnnotation.call(this, params); michael@0: michael@0: if (params.data) { michael@0: return; michael@0: } michael@0: michael@0: var dict = params.dict; michael@0: var data = this.data; michael@0: michael@0: var action = dict.get('A'); michael@0: if (action) { michael@0: var linkType = action.get('S').name; michael@0: if (linkType === 'URI') { michael@0: var url = action.get('URI'); michael@0: if (isName(url)) { michael@0: // Some bad PDFs do not put parentheses around relative URLs. michael@0: url = '/' + url.name; michael@0: } else if (url) { michael@0: url = addDefaultProtocolToUrl(url); michael@0: } michael@0: // TODO: pdf spec mentions urls can be relative to a Base michael@0: // entry in the dictionary. michael@0: if (!isValidUrl(url, false)) { michael@0: url = ''; michael@0: } michael@0: data.url = url; michael@0: } else if (linkType === 'GoTo') { michael@0: data.dest = action.get('D'); michael@0: } else if (linkType === 'GoToR') { michael@0: var urlDict = action.get('F'); michael@0: if (isDict(urlDict)) { michael@0: // We assume that the 'url' is a Filspec dictionary michael@0: // and fetch the url without checking any further michael@0: url = urlDict.get('F') || ''; michael@0: } michael@0: michael@0: // TODO: pdf reference says that GoToR michael@0: // can also have 'NewWindow' attribute michael@0: if (!isValidUrl(url, false)) { michael@0: url = ''; michael@0: } michael@0: data.url = url; michael@0: data.dest = action.get('D'); michael@0: } else if (linkType === 'Named') { michael@0: data.action = action.get('N').name; michael@0: } else { michael@0: warn('unrecognized link type: ' + linkType); michael@0: } michael@0: } else if (dict.has('Dest')) { michael@0: // simple destination link michael@0: var dest = dict.get('Dest'); michael@0: data.dest = isName(dest) ? dest.name : dest; michael@0: } michael@0: } michael@0: michael@0: // Lets URLs beginning with 'www.' default to using the 'http://' protocol. michael@0: function addDefaultProtocolToUrl(url) { michael@0: if (url && url.indexOf('www.') === 0) { michael@0: return ('http://' + url); michael@0: } michael@0: return url; michael@0: } michael@0: michael@0: Util.inherit(LinkAnnotation, InteractiveAnnotation, { michael@0: hasOperatorList: function LinkAnnotation_hasOperatorList() { michael@0: return false; michael@0: }, michael@0: michael@0: getHtmlElement: function LinkAnnotation_getHtmlElement(commonObjs) { michael@0: michael@0: var container = this.initContainer(); michael@0: container.className = 'annotLink'; michael@0: michael@0: var item = this.data; michael@0: michael@0: container.style.borderColor = item.colorCssRgb; michael@0: container.style.borderStyle = 'solid'; michael@0: michael@0: var link = document.createElement('a'); michael@0: link.href = link.title = this.data.url || ''; michael@0: michael@0: container.appendChild(link); michael@0: michael@0: return container; michael@0: } michael@0: }); michael@0: michael@0: return LinkAnnotation; michael@0: })(); michael@0: michael@0: michael@0: /** michael@0: * The maximum allowed image size in total pixels e.g. width * height. Images michael@0: * above this value will not be drawn. Use -1 for no limit. michael@0: * @var {number} michael@0: */ michael@0: PDFJS.maxImageSize = (PDFJS.maxImageSize === undefined ? michael@0: -1 : PDFJS.maxImageSize); michael@0: michael@0: /** michael@0: * The url of where the predefined Adobe CMaps are located. Include trailing michael@0: * slash. michael@0: * @var {string} michael@0: */ michael@0: PDFJS.cMapUrl = (PDFJS.cMapUrl === undefined ? null : PDFJS.cMapUrl); michael@0: michael@0: /** michael@0: * Specifies if CMaps are binary packed. michael@0: * @var {boolean} michael@0: */ michael@0: PDFJS.cMapPacked = PDFJS.cMapPacked === undefined ? false : PDFJS.cMapPacked; michael@0: michael@0: /* michael@0: * By default fonts are converted to OpenType fonts and loaded via font face michael@0: * rules. If disabled, the font will be rendered using a built in font renderer michael@0: * that constructs the glyphs with primitive path commands. michael@0: * @var {boolean} michael@0: */ michael@0: PDFJS.disableFontFace = (PDFJS.disableFontFace === undefined ? michael@0: false : PDFJS.disableFontFace); michael@0: michael@0: /** michael@0: * Path for image resources, mainly for annotation icons. Include trailing michael@0: * slash. michael@0: * @var {string} michael@0: */ michael@0: PDFJS.imageResourcesPath = (PDFJS.imageResourcesPath === undefined ? michael@0: '' : PDFJS.imageResourcesPath); michael@0: michael@0: /** michael@0: * Disable the web worker and run all code on the main thread. This will happen michael@0: * automatically if the browser doesn't support workers or sending typed arrays michael@0: * to workers. michael@0: * @var {boolean} michael@0: */ michael@0: PDFJS.disableWorker = (PDFJS.disableWorker === undefined ? michael@0: false : PDFJS.disableWorker); michael@0: michael@0: /** michael@0: * Path and filename of the worker file. Required when the worker is enabled in michael@0: * development mode. If unspecified in the production build, the worker will be michael@0: * loaded based on the location of the pdf.js file. michael@0: * @var {string} michael@0: */ michael@0: PDFJS.workerSrc = (PDFJS.workerSrc === undefined ? null : PDFJS.workerSrc); michael@0: michael@0: /** michael@0: * Disable range request loading of PDF files. When enabled and if the server michael@0: * supports partial content requests then the PDF will be fetched in chunks. michael@0: * Enabled (false) by default. michael@0: * @var {boolean} michael@0: */ michael@0: PDFJS.disableRange = (PDFJS.disableRange === undefined ? michael@0: false : PDFJS.disableRange); michael@0: michael@0: /** michael@0: * Disable pre-fetching of PDF file data. When range requests are enabled PDF.js michael@0: * will automatically keep fetching more data even if it isn't needed to display michael@0: * the current page. This default behavior can be disabled. michael@0: * @var {boolean} michael@0: */ michael@0: PDFJS.disableAutoFetch = (PDFJS.disableAutoFetch === undefined ? michael@0: false : PDFJS.disableAutoFetch); michael@0: michael@0: /** michael@0: * Enables special hooks for debugging PDF.js. michael@0: * @var {boolean} michael@0: */ michael@0: PDFJS.pdfBug = (PDFJS.pdfBug === undefined ? false : PDFJS.pdfBug); michael@0: michael@0: /** michael@0: * Enables transfer usage in postMessage for ArrayBuffers. michael@0: * @var {boolean} michael@0: */ michael@0: PDFJS.postMessageTransfers = (PDFJS.postMessageTransfers === undefined ? michael@0: true : PDFJS.postMessageTransfers); michael@0: michael@0: /** michael@0: * Disables URL.createObjectURL usage. michael@0: * @var {boolean} michael@0: */ michael@0: PDFJS.disableCreateObjectURL = (PDFJS.disableCreateObjectURL === undefined ? michael@0: false : PDFJS.disableCreateObjectURL); michael@0: michael@0: /** michael@0: * Disables WebGL usage. michael@0: * @var {boolean} michael@0: */ michael@0: PDFJS.disableWebGL = (PDFJS.disableWebGL === undefined ? michael@0: true : PDFJS.disableWebGL); michael@0: michael@0: /** michael@0: * Controls the logging level. michael@0: * The constants from PDFJS.VERBOSITY_LEVELS should be used: michael@0: * - errors michael@0: * - warnings [default] michael@0: * - infos michael@0: * @var {number} michael@0: */ michael@0: PDFJS.verbosity = (PDFJS.verbosity === undefined ? michael@0: PDFJS.VERBOSITY_LEVELS.warnings : PDFJS.verbosity); michael@0: michael@0: /** michael@0: * Document initialization / loading parameters object. michael@0: * michael@0: * @typedef {Object} DocumentInitParameters michael@0: * @property {string} url - The URL of the PDF. michael@0: * @property {TypedArray} data - A typed array with PDF data. michael@0: * @property {Object} httpHeaders - Basic authentication headers. michael@0: * @property {boolean} withCredentials - Indicates whether or not cross-site michael@0: * Access-Control requests should be made using credentials such as cookies michael@0: * or authorization headers. The default is false. michael@0: * @property {string} password - For decrypting password-protected PDFs. michael@0: * @property {TypedArray} initialData - A typed array with the first portion or michael@0: * all of the pdf data. Used by the extension since some data is already michael@0: * loaded before the switch to range requests. michael@0: */ michael@0: michael@0: /** michael@0: * This is the main entry point for loading a PDF and interacting with it. michael@0: * NOTE: If a URL is used to fetch the PDF data a standard XMLHttpRequest(XHR) michael@0: * is used, which means it must follow the same origin rules that any XHR does michael@0: * e.g. No cross domain requests without CORS. michael@0: * michael@0: * @param {string|TypedArray|DocumentInitParameters} source Can be a url to michael@0: * where a PDF is located, a typed array (Uint8Array) already populated with michael@0: * data or parameter object. michael@0: * michael@0: * @param {Object} pdfDataRangeTransport is optional. It is used if you want michael@0: * to manually serve range requests for data in the PDF. See viewer.js for michael@0: * an example of pdfDataRangeTransport's interface. michael@0: * michael@0: * @param {function} passwordCallback is optional. It is used to request a michael@0: * password if wrong or no password was provided. The callback receives two michael@0: * parameters: function that needs to be called with new password and reason michael@0: * (see {PasswordResponses}). michael@0: * michael@0: * @return {Promise} A promise that is resolved with {@link PDFDocumentProxy} michael@0: * object. michael@0: */ michael@0: PDFJS.getDocument = function getDocument(source, michael@0: pdfDataRangeTransport, michael@0: passwordCallback, michael@0: progressCallback) { michael@0: var workerInitializedPromise, workerReadyPromise, transport; michael@0: michael@0: if (typeof source === 'string') { michael@0: source = { url: source }; michael@0: } else if (isArrayBuffer(source)) { michael@0: source = { data: source }; michael@0: } else if (typeof source !== 'object') { michael@0: error('Invalid parameter in getDocument, need either Uint8Array, ' + michael@0: 'string or a parameter object'); michael@0: } michael@0: michael@0: if (!source.url && !source.data) { michael@0: error('Invalid parameter array, need either .data or .url'); michael@0: } michael@0: michael@0: // copy/use all keys as is except 'url' -- full path is required michael@0: var params = {}; michael@0: for (var key in source) { michael@0: if (key === 'url' && typeof window !== 'undefined') { michael@0: params[key] = combineUrl(window.location.href, source[key]); michael@0: continue; michael@0: } michael@0: params[key] = source[key]; michael@0: } michael@0: michael@0: workerInitializedPromise = new PDFJS.LegacyPromise(); michael@0: workerReadyPromise = new PDFJS.LegacyPromise(); michael@0: transport = new WorkerTransport(workerInitializedPromise, workerReadyPromise, michael@0: pdfDataRangeTransport, progressCallback); michael@0: workerInitializedPromise.then(function transportInitialized() { michael@0: transport.passwordCallback = passwordCallback; michael@0: transport.fetchDocument(params); michael@0: }); michael@0: return workerReadyPromise; michael@0: }; michael@0: michael@0: /** michael@0: * Proxy to a PDFDocument in the worker thread. Also, contains commonly used michael@0: * properties that can be read synchronously. michael@0: * @class michael@0: */ michael@0: var PDFDocumentProxy = (function PDFDocumentProxyClosure() { michael@0: function PDFDocumentProxy(pdfInfo, transport) { michael@0: this.pdfInfo = pdfInfo; michael@0: this.transport = transport; michael@0: } michael@0: PDFDocumentProxy.prototype = /** @lends PDFDocumentProxy.prototype */ { michael@0: /** michael@0: * @return {number} Total number of pages the PDF contains. michael@0: */ michael@0: get numPages() { michael@0: return this.pdfInfo.numPages; michael@0: }, michael@0: /** michael@0: * @return {string} A unique ID to identify a PDF. Not guaranteed to be michael@0: * unique. michael@0: */ michael@0: get fingerprint() { michael@0: return this.pdfInfo.fingerprint; michael@0: }, michael@0: /** michael@0: * @param {number} pageNumber The page number to get. The first page is 1. michael@0: * @return {Promise} A promise that is resolved with a {@link PDFPageProxy} michael@0: * object. michael@0: */ michael@0: getPage: function PDFDocumentProxy_getPage(pageNumber) { michael@0: return this.transport.getPage(pageNumber); michael@0: }, michael@0: /** michael@0: * @param {{num: number, gen: number}} ref The page reference. Must have michael@0: * the 'num' and 'gen' properties. michael@0: * @return {Promise} A promise that is resolved with the page index that is michael@0: * associated with the reference. michael@0: */ michael@0: getPageIndex: function PDFDocumentProxy_getPageIndex(ref) { michael@0: return this.transport.getPageIndex(ref); michael@0: }, michael@0: /** michael@0: * @return {Promise} A promise that is resolved with a lookup table for michael@0: * mapping named destinations to reference numbers. michael@0: */ michael@0: getDestinations: function PDFDocumentProxy_getDestinations() { michael@0: return this.transport.getDestinations(); michael@0: }, michael@0: /** michael@0: * @return {Promise} A promise that is resolved with a lookup table for michael@0: * mapping named attachments to their content. michael@0: */ michael@0: getAttachments: function PDFDocumentProxy_getAttachments() { michael@0: return this.transport.getAttachments(); michael@0: }, michael@0: /** michael@0: * @return {Promise} A promise that is resolved with an array of all the michael@0: * JavaScript strings in the name tree. michael@0: */ michael@0: getJavaScript: function PDFDocumentProxy_getJavaScript() { michael@0: var promise = new PDFJS.LegacyPromise(); michael@0: var js = this.pdfInfo.javaScript; michael@0: promise.resolve(js); michael@0: return promise; michael@0: }, michael@0: /** michael@0: * @return {Promise} A promise that is resolved with an {Array} that is a michael@0: * tree outline (if it has one) of the PDF. The tree is in the format of: michael@0: * [ michael@0: * { michael@0: * title: string, michael@0: * bold: boolean, michael@0: * italic: boolean, michael@0: * color: rgb array, michael@0: * dest: dest obj, michael@0: * items: array of more items like this michael@0: * }, michael@0: * ... michael@0: * ]. michael@0: */ michael@0: getOutline: function PDFDocumentProxy_getOutline() { michael@0: var promise = new PDFJS.LegacyPromise(); michael@0: var outline = this.pdfInfo.outline; michael@0: promise.resolve(outline); michael@0: return promise; michael@0: }, michael@0: /** michael@0: * @return {Promise} A promise that is resolved with an {Object} that has michael@0: * info and metadata properties. Info is an {Object} filled with anything michael@0: * available in the information dictionary and similarly metadata is a michael@0: * {Metadata} object with information from the metadata section of the PDF. michael@0: */ michael@0: getMetadata: function PDFDocumentProxy_getMetadata() { michael@0: var promise = new PDFJS.LegacyPromise(); michael@0: var info = this.pdfInfo.info; michael@0: var metadata = this.pdfInfo.metadata; michael@0: promise.resolve({ michael@0: info: info, michael@0: metadata: (metadata ? new PDFJS.Metadata(metadata) : null) michael@0: }); michael@0: return promise; michael@0: }, michael@0: /** michael@0: * @return {Promise} A promise that is resolved with a TypedArray that has michael@0: * the raw data from the PDF. michael@0: */ michael@0: getData: function PDFDocumentProxy_getData() { michael@0: var promise = new PDFJS.LegacyPromise(); michael@0: this.transport.getData(promise); michael@0: return promise; michael@0: }, michael@0: /** michael@0: * @return {Promise} A promise that is resolved when the document's data michael@0: * is loaded. It is resolved with an {Object} that contains the length michael@0: * property that indicates size of the PDF data in bytes. michael@0: */ michael@0: getDownloadInfo: function PDFDocumentProxy_getDownloadInfo() { michael@0: return this.transport.downloadInfoPromise; michael@0: }, michael@0: /** michael@0: * Cleans up resources allocated by the document, e.g. created @font-face. michael@0: */ michael@0: cleanup: function PDFDocumentProxy_cleanup() { michael@0: this.transport.startCleanup(); michael@0: }, michael@0: /** michael@0: * Destroys current document instance and terminates worker. michael@0: */ michael@0: destroy: function PDFDocumentProxy_destroy() { michael@0: this.transport.destroy(); michael@0: } michael@0: }; michael@0: return PDFDocumentProxy; michael@0: })(); michael@0: michael@0: /** michael@0: * Page text content. michael@0: * michael@0: * @typedef {Object} TextContent michael@0: * @property {array} items - array of {@link TextItem} michael@0: * @property {Object} styles - {@link TextStyles} objects, indexed by font michael@0: * name. michael@0: */ michael@0: michael@0: /** michael@0: * Page text content part. michael@0: * michael@0: * @typedef {Object} TextItem michael@0: * @property {string} str - text content. michael@0: * @property {string} dir - text direction: 'ttb', 'ltr' or 'rtl'. michael@0: * @property {array} transform - transformation matrix. michael@0: * @property {number} width - width in device space. michael@0: * @property {number} height - height in device space. michael@0: * @property {string} fontName - font name used by pdf.js for converted font. michael@0: */ michael@0: michael@0: /** michael@0: * Text style. michael@0: * michael@0: * @typedef {Object} TextStyle michael@0: * @property {number} ascent - font ascent. michael@0: * @property {number} descent - font descent. michael@0: * @property {boolean} vertical - text is in vertical mode. michael@0: * @property {string} fontFamily - possible font family michael@0: */ michael@0: michael@0: /** michael@0: * Page render parameters. michael@0: * michael@0: * @typedef {Object} RenderParameters michael@0: * @property {Object} canvasContext - A 2D context of a DOM Canvas object. michael@0: * @property {PageViewport} viewport - Rendering viewport obtained by michael@0: * calling of PDFPage.getViewport method. michael@0: * @property {string} intent - Rendering intent, can be 'display' or 'print' michael@0: * (default value is 'display'). michael@0: * @property {Object} imageLayer - (optional) An object that has beginLayout, michael@0: * endLayout and appendImage functions. michael@0: * @property {function} continueCallback - (optional) A function that will be michael@0: * called each time the rendering is paused. To continue michael@0: * rendering call the function that is the first argument michael@0: * to the callback. michael@0: */ michael@0: michael@0: /** michael@0: * Proxy to a PDFPage in the worker thread. michael@0: * @class michael@0: */ michael@0: var PDFPageProxy = (function PDFPageProxyClosure() { michael@0: function PDFPageProxy(pageInfo, transport) { michael@0: this.pageInfo = pageInfo; michael@0: this.transport = transport; michael@0: this.stats = new StatTimer(); michael@0: this.stats.enabled = !!globalScope.PDFJS.enableStats; michael@0: this.commonObjs = transport.commonObjs; michael@0: this.objs = new PDFObjects(); michael@0: this.cleanupAfterRender = false; michael@0: this.pendingDestroy = false; michael@0: this.intentStates = {}; michael@0: } michael@0: PDFPageProxy.prototype = /** @lends PDFPageProxy.prototype */ { michael@0: /** michael@0: * @return {number} Page number of the page. First page is 1. michael@0: */ michael@0: get pageNumber() { michael@0: return this.pageInfo.pageIndex + 1; michael@0: }, michael@0: /** michael@0: * @return {number} The number of degrees the page is rotated clockwise. michael@0: */ michael@0: get rotate() { michael@0: return this.pageInfo.rotate; michael@0: }, michael@0: /** michael@0: * @return {Object} The reference that points to this page. It has 'num' and michael@0: * 'gen' properties. michael@0: */ michael@0: get ref() { michael@0: return this.pageInfo.ref; michael@0: }, michael@0: /** michael@0: * @return {Array} An array of the visible portion of the PDF page in the michael@0: * user space units - [x1, y1, x2, y2]. michael@0: */ michael@0: get view() { michael@0: return this.pageInfo.view; michael@0: }, michael@0: /** michael@0: * @param {number} scale The desired scale of the viewport. michael@0: * @param {number} rotate Degrees to rotate the viewport. If omitted this michael@0: * defaults to the page rotation. michael@0: * @return {PageViewport} Contains 'width' and 'height' properties along michael@0: * with transforms required for rendering. michael@0: */ michael@0: getViewport: function PDFPageProxy_getViewport(scale, rotate) { michael@0: if (arguments.length < 2) { michael@0: rotate = this.rotate; michael@0: } michael@0: return new PDFJS.PageViewport(this.view, scale, rotate, 0, 0); michael@0: }, michael@0: /** michael@0: * @return {Promise} A promise that is resolved with an {Array} of the michael@0: * annotation objects. michael@0: */ michael@0: getAnnotations: function PDFPageProxy_getAnnotations() { michael@0: if (this.annotationsPromise) { michael@0: return this.annotationsPromise; michael@0: } michael@0: michael@0: var promise = new PDFJS.LegacyPromise(); michael@0: this.annotationsPromise = promise; michael@0: this.transport.getAnnotations(this.pageInfo.pageIndex); michael@0: return promise; michael@0: }, michael@0: /** michael@0: * Begins the process of rendering a page to the desired context. michael@0: * @param {RenderParameters} params Page render parameters. michael@0: * @return {RenderTask} An object that contains the promise, which michael@0: * is resolved when the page finishes rendering. michael@0: */ michael@0: render: function PDFPageProxy_render(params) { michael@0: var stats = this.stats; michael@0: stats.time('Overall'); michael@0: michael@0: // If there was a pending destroy cancel it so no cleanup happens during michael@0: // this call to render. michael@0: this.pendingDestroy = false; michael@0: michael@0: var renderingIntent = ('intent' in params ? michael@0: (params.intent == 'print' ? 'print' : 'display') : 'display'); michael@0: michael@0: if (!this.intentStates[renderingIntent]) { michael@0: this.intentStates[renderingIntent] = {}; michael@0: } michael@0: var intentState = this.intentStates[renderingIntent]; michael@0: michael@0: // If there is no displayReadyPromise yet, then the operatorList was never michael@0: // requested before. Make the request and create the promise. michael@0: if (!intentState.displayReadyPromise) { michael@0: intentState.receivingOperatorList = true; michael@0: intentState.displayReadyPromise = new LegacyPromise(); michael@0: intentState.operatorList = { michael@0: fnArray: [], michael@0: argsArray: [], michael@0: lastChunk: false michael@0: }; michael@0: michael@0: this.stats.time('Page Request'); michael@0: this.transport.messageHandler.send('RenderPageRequest', { michael@0: pageIndex: this.pageNumber - 1, michael@0: intent: renderingIntent michael@0: }); michael@0: } michael@0: michael@0: var internalRenderTask = new InternalRenderTask(complete, params, michael@0: this.objs, michael@0: this.commonObjs, michael@0: intentState.operatorList, michael@0: this.pageNumber); michael@0: if (!intentState.renderTasks) { michael@0: intentState.renderTasks = []; michael@0: } michael@0: intentState.renderTasks.push(internalRenderTask); michael@0: var renderTask = new RenderTask(internalRenderTask); michael@0: michael@0: var self = this; michael@0: intentState.displayReadyPromise.then( michael@0: function pageDisplayReadyPromise(transparency) { michael@0: if (self.pendingDestroy) { michael@0: complete(); michael@0: return; michael@0: } michael@0: stats.time('Rendering'); michael@0: internalRenderTask.initalizeGraphics(transparency); michael@0: internalRenderTask.operatorListChanged(); michael@0: }, michael@0: function pageDisplayReadPromiseError(reason) { michael@0: complete(reason); michael@0: } michael@0: ); michael@0: michael@0: function complete(error) { michael@0: var i = intentState.renderTasks.indexOf(internalRenderTask); michael@0: if (i >= 0) { michael@0: intentState.renderTasks.splice(i, 1); michael@0: } michael@0: michael@0: if (self.cleanupAfterRender) { michael@0: self.pendingDestroy = true; michael@0: } michael@0: self._tryDestroy(); michael@0: michael@0: if (error) { michael@0: renderTask.promise.reject(error); michael@0: } else { michael@0: renderTask.promise.resolve(); michael@0: } michael@0: stats.timeEnd('Rendering'); michael@0: stats.timeEnd('Overall'); michael@0: } michael@0: michael@0: return renderTask; michael@0: }, michael@0: /** michael@0: * @return {Promise} That is resolved a {@link TextContent} michael@0: * object that represent the page text content. michael@0: */ michael@0: getTextContent: function PDFPageProxy_getTextContent() { michael@0: var promise = new PDFJS.LegacyPromise(); michael@0: this.transport.messageHandler.send('GetTextContent', { michael@0: pageIndex: this.pageNumber - 1 michael@0: }, michael@0: function textContentCallback(textContent) { michael@0: promise.resolve(textContent); michael@0: } michael@0: ); michael@0: return promise; michael@0: }, michael@0: /** michael@0: * Destroys resources allocated by the page. michael@0: */ michael@0: destroy: function PDFPageProxy_destroy() { michael@0: this.pendingDestroy = true; michael@0: this._tryDestroy(); michael@0: }, michael@0: /** michael@0: * For internal use only. Attempts to clean up if rendering is in a state michael@0: * where that's possible. michael@0: * @ignore michael@0: */ michael@0: _tryDestroy: function PDFPageProxy__destroy() { michael@0: if (!this.pendingDestroy || michael@0: Object.keys(this.intentStates).some(function(intent) { michael@0: var intentState = this.intentStates[intent]; michael@0: return (intentState.renderTasks.length !== 0 || michael@0: intentState.receivingOperatorList); michael@0: }, this)) { michael@0: return; michael@0: } michael@0: michael@0: Object.keys(this.intentStates).forEach(function(intent) { michael@0: delete this.intentStates[intent]; michael@0: }, this); michael@0: this.objs.clear(); michael@0: this.pendingDestroy = false; michael@0: }, michael@0: /** michael@0: * For internal use only. michael@0: * @ignore michael@0: */ michael@0: _startRenderPage: function PDFPageProxy_startRenderPage(transparency, michael@0: intent) { michael@0: var intentState = this.intentStates[intent]; michael@0: intentState.displayReadyPromise.resolve(transparency); michael@0: }, michael@0: /** michael@0: * For internal use only. michael@0: * @ignore michael@0: */ michael@0: _renderPageChunk: function PDFPageProxy_renderPageChunk(operatorListChunk, michael@0: intent) { michael@0: var intentState = this.intentStates[intent]; michael@0: var i, ii; michael@0: // Add the new chunk to the current operator list. michael@0: for (i = 0, ii = operatorListChunk.length; i < ii; i++) { michael@0: intentState.operatorList.fnArray.push(operatorListChunk.fnArray[i]); michael@0: intentState.operatorList.argsArray.push( michael@0: operatorListChunk.argsArray[i]); michael@0: } michael@0: intentState.operatorList.lastChunk = operatorListChunk.lastChunk; michael@0: michael@0: // Notify all the rendering tasks there are more operators to be consumed. michael@0: for (i = 0; i < intentState.renderTasks.length; i++) { michael@0: intentState.renderTasks[i].operatorListChanged(); michael@0: } michael@0: michael@0: if (operatorListChunk.lastChunk) { michael@0: intentState.receivingOperatorList = false; michael@0: this._tryDestroy(); michael@0: } michael@0: } michael@0: }; michael@0: return PDFPageProxy; michael@0: })(); michael@0: michael@0: /** michael@0: * For internal use only. michael@0: * @ignore michael@0: */ michael@0: var WorkerTransport = (function WorkerTransportClosure() { michael@0: function WorkerTransport(workerInitializedPromise, workerReadyPromise, michael@0: pdfDataRangeTransport, progressCallback) { michael@0: this.pdfDataRangeTransport = pdfDataRangeTransport; michael@0: michael@0: this.workerReadyPromise = workerReadyPromise; michael@0: this.progressCallback = progressCallback; michael@0: this.commonObjs = new PDFObjects(); michael@0: michael@0: this.pageCache = []; michael@0: this.pagePromises = []; michael@0: this.downloadInfoPromise = new PDFJS.LegacyPromise(); michael@0: this.passwordCallback = null; michael@0: michael@0: // If worker support isn't disabled explicit and the browser has worker michael@0: // support, create a new web worker and test if it/the browser fullfills michael@0: // all requirements to run parts of pdf.js in a web worker. michael@0: // Right now, the requirement is, that an Uint8Array is still an Uint8Array michael@0: // as it arrives on the worker. Chrome added this with version 15. michael@0: if (!globalScope.PDFJS.disableWorker && typeof Worker !== 'undefined') { michael@0: var workerSrc = PDFJS.workerSrc; michael@0: if (!workerSrc) { michael@0: error('No PDFJS.workerSrc specified'); michael@0: } michael@0: michael@0: try { michael@0: // Some versions of FF can't create a worker on localhost, see: michael@0: // https://bugzilla.mozilla.org/show_bug.cgi?id=683280 michael@0: var worker = new Worker(workerSrc); michael@0: var messageHandler = new MessageHandler('main', worker); michael@0: this.messageHandler = messageHandler; michael@0: michael@0: messageHandler.on('test', function transportTest(data) { michael@0: var supportTypedArray = data && data.supportTypedArray; michael@0: if (supportTypedArray) { michael@0: this.worker = worker; michael@0: if (!data.supportTransfers) { michael@0: PDFJS.postMessageTransfers = false; michael@0: } michael@0: this.setupMessageHandler(messageHandler); michael@0: workerInitializedPromise.resolve(); michael@0: } else { michael@0: globalScope.PDFJS.disableWorker = true; michael@0: this.loadFakeWorkerFiles().then(function() { michael@0: this.setupFakeWorker(); michael@0: workerInitializedPromise.resolve(); michael@0: }.bind(this)); michael@0: } michael@0: }.bind(this)); michael@0: michael@0: var testObj = new Uint8Array([PDFJS.postMessageTransfers ? 255 : 0]); michael@0: // Some versions of Opera throw a DATA_CLONE_ERR on serializing the michael@0: // typed array. Also, checking if we can use transfers. michael@0: try { michael@0: messageHandler.send('test', testObj, null, [testObj.buffer]); michael@0: } catch (ex) { michael@0: info('Cannot use postMessage transfers'); michael@0: testObj[0] = 0; michael@0: messageHandler.send('test', testObj); michael@0: } michael@0: return; michael@0: } catch (e) { michael@0: info('The worker has been disabled.'); michael@0: } michael@0: } michael@0: // Either workers are disabled, not supported or have thrown an exception. michael@0: // Thus, we fallback to a faked worker. michael@0: globalScope.PDFJS.disableWorker = true; michael@0: this.loadFakeWorkerFiles().then(function() { michael@0: this.setupFakeWorker(); michael@0: workerInitializedPromise.resolve(); michael@0: }.bind(this)); michael@0: } michael@0: WorkerTransport.prototype = { michael@0: destroy: function WorkerTransport_destroy() { michael@0: this.pageCache = []; michael@0: this.pagePromises = []; michael@0: var self = this; michael@0: this.messageHandler.send('Terminate', null, function () { michael@0: FontLoader.clear(); michael@0: if (self.worker) { michael@0: self.worker.terminate(); michael@0: } michael@0: }); michael@0: }, michael@0: michael@0: loadFakeWorkerFiles: function WorkerTransport_loadFakeWorkerFiles() { michael@0: if (!PDFJS.fakeWorkerFilesLoadedPromise) { michael@0: PDFJS.fakeWorkerFilesLoadedPromise = new LegacyPromise(); michael@0: // In the developer build load worker_loader which in turn loads all the michael@0: // other files and resolves the promise. In production only the michael@0: // pdf.worker.js file is needed. michael@0: Util.loadScript(PDFJS.workerSrc, function() { michael@0: PDFJS.fakeWorkerFilesLoadedPromise.resolve(); michael@0: }); michael@0: } michael@0: return PDFJS.fakeWorkerFilesLoadedPromise; michael@0: }, michael@0: michael@0: setupFakeWorker: function WorkerTransport_setupFakeWorker() { michael@0: warn('Setting up fake worker.'); michael@0: // If we don't use a worker, just post/sendMessage to the main thread. michael@0: var fakeWorker = { michael@0: postMessage: function WorkerTransport_postMessage(obj) { michael@0: fakeWorker.onmessage({data: obj}); michael@0: }, michael@0: terminate: function WorkerTransport_terminate() {} michael@0: }; michael@0: michael@0: var messageHandler = new MessageHandler('main', fakeWorker); michael@0: this.setupMessageHandler(messageHandler); michael@0: michael@0: // If the main thread is our worker, setup the handling for the messages michael@0: // the main thread sends to it self. michael@0: PDFJS.WorkerMessageHandler.setup(messageHandler); michael@0: }, michael@0: michael@0: setupMessageHandler: michael@0: function WorkerTransport_setupMessageHandler(messageHandler) { michael@0: this.messageHandler = messageHandler; michael@0: michael@0: function updatePassword(password) { michael@0: messageHandler.send('UpdatePassword', password); michael@0: } michael@0: michael@0: var pdfDataRangeTransport = this.pdfDataRangeTransport; michael@0: if (pdfDataRangeTransport) { michael@0: pdfDataRangeTransport.addRangeListener(function(begin, chunk) { michael@0: messageHandler.send('OnDataRange', { michael@0: begin: begin, michael@0: chunk: chunk michael@0: }); michael@0: }); michael@0: michael@0: pdfDataRangeTransport.addProgressListener(function(loaded) { michael@0: messageHandler.send('OnDataProgress', { michael@0: loaded: loaded michael@0: }); michael@0: }); michael@0: michael@0: messageHandler.on('RequestDataRange', michael@0: function transportDataRange(data) { michael@0: pdfDataRangeTransport.requestDataRange(data.begin, data.end); michael@0: }, this); michael@0: } michael@0: michael@0: messageHandler.on('GetDoc', function transportDoc(data) { michael@0: var pdfInfo = data.pdfInfo; michael@0: this.numPages = data.pdfInfo.numPages; michael@0: var pdfDocument = new PDFDocumentProxy(pdfInfo, this); michael@0: this.pdfDocument = pdfDocument; michael@0: this.workerReadyPromise.resolve(pdfDocument); michael@0: }, this); michael@0: michael@0: messageHandler.on('NeedPassword', function transportPassword(data) { michael@0: if (this.passwordCallback) { michael@0: return this.passwordCallback(updatePassword, michael@0: PasswordResponses.NEED_PASSWORD); michael@0: } michael@0: this.workerReadyPromise.reject(data.exception.message, data.exception); michael@0: }, this); michael@0: michael@0: messageHandler.on('IncorrectPassword', function transportBadPass(data) { michael@0: if (this.passwordCallback) { michael@0: return this.passwordCallback(updatePassword, michael@0: PasswordResponses.INCORRECT_PASSWORD); michael@0: } michael@0: this.workerReadyPromise.reject(data.exception.message, data.exception); michael@0: }, this); michael@0: michael@0: messageHandler.on('InvalidPDF', function transportInvalidPDF(data) { michael@0: this.workerReadyPromise.reject(data.exception.name, data.exception); michael@0: }, this); michael@0: michael@0: messageHandler.on('MissingPDF', function transportMissingPDF(data) { michael@0: this.workerReadyPromise.reject(data.exception.message, data.exception); michael@0: }, this); michael@0: michael@0: messageHandler.on('UnknownError', function transportUnknownError(data) { michael@0: this.workerReadyPromise.reject(data.exception.message, data.exception); michael@0: }, this); michael@0: michael@0: messageHandler.on('DataLoaded', function transportPage(data) { michael@0: this.downloadInfoPromise.resolve(data); michael@0: }, this); michael@0: michael@0: messageHandler.on('GetPage', function transportPage(data) { michael@0: var pageInfo = data.pageInfo; michael@0: var page = new PDFPageProxy(pageInfo, this); michael@0: this.pageCache[pageInfo.pageIndex] = page; michael@0: var promise = this.pagePromises[pageInfo.pageIndex]; michael@0: promise.resolve(page); michael@0: }, this); michael@0: michael@0: messageHandler.on('GetAnnotations', function transportAnnotations(data) { michael@0: var annotations = data.annotations; michael@0: var promise = this.pageCache[data.pageIndex].annotationsPromise; michael@0: promise.resolve(annotations); michael@0: }, this); michael@0: michael@0: messageHandler.on('StartRenderPage', function transportRender(data) { michael@0: var page = this.pageCache[data.pageIndex]; michael@0: michael@0: page.stats.timeEnd('Page Request'); michael@0: page._startRenderPage(data.transparency, data.intent); michael@0: }, this); michael@0: michael@0: messageHandler.on('RenderPageChunk', function transportRender(data) { michael@0: var page = this.pageCache[data.pageIndex]; michael@0: michael@0: page._renderPageChunk(data.operatorList, data.intent); michael@0: }, this); michael@0: michael@0: messageHandler.on('commonobj', function transportObj(data) { michael@0: var id = data[0]; michael@0: var type = data[1]; michael@0: if (this.commonObjs.hasData(id)) { michael@0: return; michael@0: } michael@0: michael@0: switch (type) { michael@0: case 'Font': michael@0: var exportedData = data[2]; michael@0: michael@0: var font; michael@0: if ('error' in exportedData) { michael@0: var error = exportedData.error; michael@0: warn('Error during font loading: ' + error); michael@0: this.commonObjs.resolve(id, error); michael@0: break; michael@0: } else { michael@0: font = new FontFace(exportedData); michael@0: } michael@0: michael@0: FontLoader.bind( michael@0: [font], michael@0: function fontReady(fontObjs) { michael@0: this.commonObjs.resolve(id, font); michael@0: }.bind(this) michael@0: ); michael@0: break; michael@0: case 'FontPath': michael@0: this.commonObjs.resolve(id, data[2]); michael@0: break; michael@0: default: michael@0: error('Got unknown common object type ' + type); michael@0: } michael@0: }, this); michael@0: michael@0: messageHandler.on('obj', function transportObj(data) { michael@0: var id = data[0]; michael@0: var pageIndex = data[1]; michael@0: var type = data[2]; michael@0: var pageProxy = this.pageCache[pageIndex]; michael@0: var imageData; michael@0: if (pageProxy.objs.hasData(id)) { michael@0: return; michael@0: } michael@0: michael@0: switch (type) { michael@0: case 'JpegStream': michael@0: imageData = data[3]; michael@0: loadJpegStream(id, imageData, pageProxy.objs); michael@0: break; michael@0: case 'Image': michael@0: imageData = data[3]; michael@0: pageProxy.objs.resolve(id, imageData); michael@0: michael@0: // heuristics that will allow not to store large data michael@0: var MAX_IMAGE_SIZE_TO_STORE = 8000000; michael@0: if (imageData && 'data' in imageData && michael@0: imageData.data.length > MAX_IMAGE_SIZE_TO_STORE) { michael@0: pageProxy.cleanupAfterRender = true; michael@0: } michael@0: break; michael@0: default: michael@0: error('Got unknown object type ' + type); michael@0: } michael@0: }, this); michael@0: michael@0: messageHandler.on('DocProgress', function transportDocProgress(data) { michael@0: if (this.progressCallback) { michael@0: this.progressCallback({ michael@0: loaded: data.loaded, michael@0: total: data.total michael@0: }); michael@0: } michael@0: }, this); michael@0: michael@0: messageHandler.on('DocError', function transportDocError(data) { michael@0: this.workerReadyPromise.reject(data); michael@0: }, this); michael@0: michael@0: messageHandler.on('PageError', function transportError(data, intent) { michael@0: var page = this.pageCache[data.pageNum - 1]; michael@0: var intentState = page.intentStates[intent]; michael@0: if (intentState.displayReadyPromise) { michael@0: intentState.displayReadyPromise.reject(data.error); michael@0: } else { michael@0: error(data.error); michael@0: } michael@0: }, this); michael@0: michael@0: messageHandler.on('JpegDecode', function(data, deferred) { michael@0: var imageUrl = data[0]; michael@0: var components = data[1]; michael@0: if (components != 3 && components != 1) { michael@0: error('Only 3 component or 1 component can be returned'); michael@0: } michael@0: michael@0: var img = new Image(); michael@0: img.onload = (function messageHandler_onloadClosure() { michael@0: var width = img.width; michael@0: var height = img.height; michael@0: var size = width * height; michael@0: var rgbaLength = size * 4; michael@0: var buf = new Uint8Array(size * components); michael@0: var tmpCanvas = createScratchCanvas(width, height); michael@0: var tmpCtx = tmpCanvas.getContext('2d'); michael@0: tmpCtx.drawImage(img, 0, 0); michael@0: var data = tmpCtx.getImageData(0, 0, width, height).data; michael@0: var i, j; michael@0: michael@0: if (components == 3) { michael@0: for (i = 0, j = 0; i < rgbaLength; i += 4, j += 3) { michael@0: buf[j] = data[i]; michael@0: buf[j + 1] = data[i + 1]; michael@0: buf[j + 2] = data[i + 2]; michael@0: } michael@0: } else if (components == 1) { michael@0: for (i = 0, j = 0; i < rgbaLength; i += 4, j++) { michael@0: buf[j] = data[i]; michael@0: } michael@0: } michael@0: deferred.resolve({ data: buf, width: width, height: height}); michael@0: }).bind(this); michael@0: img.src = imageUrl; michael@0: }); michael@0: }, michael@0: michael@0: fetchDocument: function WorkerTransport_fetchDocument(source) { michael@0: source.disableAutoFetch = PDFJS.disableAutoFetch; michael@0: source.chunkedViewerLoading = !!this.pdfDataRangeTransport; michael@0: this.messageHandler.send('GetDocRequest', { michael@0: source: source, michael@0: disableRange: PDFJS.disableRange, michael@0: maxImageSize: PDFJS.maxImageSize, michael@0: cMapUrl: PDFJS.cMapUrl, michael@0: cMapPacked: PDFJS.cMapPacked, michael@0: disableFontFace: PDFJS.disableFontFace, michael@0: disableCreateObjectURL: PDFJS.disableCreateObjectURL, michael@0: verbosity: PDFJS.verbosity michael@0: }); michael@0: }, michael@0: michael@0: getData: function WorkerTransport_getData(promise) { michael@0: this.messageHandler.send('GetData', null, function(data) { michael@0: promise.resolve(data); michael@0: }); michael@0: }, michael@0: michael@0: getPage: function WorkerTransport_getPage(pageNumber, promise) { michael@0: if (pageNumber <= 0 || pageNumber > this.numPages || michael@0: (pageNumber|0) !== pageNumber) { michael@0: var pagePromise = new PDFJS.LegacyPromise(); michael@0: pagePromise.reject(new Error('Invalid page request')); michael@0: return pagePromise; michael@0: } michael@0: michael@0: var pageIndex = pageNumber - 1; michael@0: if (pageIndex in this.pagePromises) { michael@0: return this.pagePromises[pageIndex]; michael@0: } michael@0: promise = new PDFJS.LegacyPromise(); michael@0: this.pagePromises[pageIndex] = promise; michael@0: this.messageHandler.send('GetPageRequest', { pageIndex: pageIndex }); michael@0: return promise; michael@0: }, michael@0: michael@0: getPageIndex: function WorkerTransport_getPageIndexByRef(ref) { michael@0: var promise = new PDFJS.LegacyPromise(); michael@0: this.messageHandler.send('GetPageIndex', { ref: ref }, michael@0: function (pageIndex) { michael@0: promise.resolve(pageIndex); michael@0: } michael@0: ); michael@0: return promise; michael@0: }, michael@0: michael@0: getAnnotations: function WorkerTransport_getAnnotations(pageIndex) { michael@0: this.messageHandler.send('GetAnnotationsRequest', michael@0: { pageIndex: pageIndex }); michael@0: }, michael@0: michael@0: getDestinations: function WorkerTransport_getDestinations() { michael@0: var promise = new PDFJS.LegacyPromise(); michael@0: this.messageHandler.send('GetDestinations', null, michael@0: function transportDestinations(destinations) { michael@0: promise.resolve(destinations); michael@0: } michael@0: ); michael@0: return promise; michael@0: }, michael@0: michael@0: getAttachments: function WorkerTransport_getAttachments() { michael@0: var promise = new PDFJS.LegacyPromise(); michael@0: this.messageHandler.send('GetAttachments', null, michael@0: function transportAttachments(attachments) { michael@0: promise.resolve(attachments); michael@0: } michael@0: ); michael@0: return promise; michael@0: }, michael@0: michael@0: startCleanup: function WorkerTransport_startCleanup() { michael@0: this.messageHandler.send('Cleanup', null, michael@0: function endCleanup() { michael@0: for (var i = 0, ii = this.pageCache.length; i < ii; i++) { michael@0: var page = this.pageCache[i]; michael@0: if (page) { michael@0: page.destroy(); michael@0: } michael@0: } michael@0: this.commonObjs.clear(); michael@0: FontLoader.clear(); michael@0: }.bind(this) michael@0: ); michael@0: } michael@0: }; michael@0: return WorkerTransport; michael@0: michael@0: })(); michael@0: michael@0: /** michael@0: * A PDF document and page is built of many objects. E.g. there are objects michael@0: * for fonts, images, rendering code and such. These objects might get processed michael@0: * inside of a worker. The `PDFObjects` implements some basic functions to michael@0: * manage these objects. michael@0: * @ignore michael@0: */ michael@0: var PDFObjects = (function PDFObjectsClosure() { michael@0: function PDFObjects() { michael@0: this.objs = {}; michael@0: } michael@0: michael@0: PDFObjects.prototype = { michael@0: /** michael@0: * Internal function. michael@0: * Ensures there is an object defined for `objId`. michael@0: */ michael@0: ensureObj: function PDFObjects_ensureObj(objId) { michael@0: if (this.objs[objId]) { michael@0: return this.objs[objId]; michael@0: } michael@0: michael@0: var obj = { michael@0: promise: new LegacyPromise(), michael@0: data: null, michael@0: resolved: false michael@0: }; michael@0: this.objs[objId] = obj; michael@0: michael@0: return obj; michael@0: }, michael@0: michael@0: /** michael@0: * If called *without* callback, this returns the data of `objId` but the michael@0: * object needs to be resolved. If it isn't, this function throws. michael@0: * michael@0: * If called *with* a callback, the callback is called with the data of the michael@0: * object once the object is resolved. That means, if you call this michael@0: * function and the object is already resolved, the callback gets called michael@0: * right away. michael@0: */ michael@0: get: function PDFObjects_get(objId, callback) { michael@0: // If there is a callback, then the get can be async and the object is michael@0: // not required to be resolved right now michael@0: if (callback) { michael@0: this.ensureObj(objId).promise.then(callback); michael@0: return null; michael@0: } michael@0: michael@0: // If there isn't a callback, the user expects to get the resolved data michael@0: // directly. michael@0: var obj = this.objs[objId]; michael@0: michael@0: // If there isn't an object yet or the object isn't resolved, then the michael@0: // data isn't ready yet! michael@0: if (!obj || !obj.resolved) { michael@0: error('Requesting object that isn\'t resolved yet ' + objId); michael@0: } michael@0: michael@0: return obj.data; michael@0: }, michael@0: michael@0: /** michael@0: * Resolves the object `objId` with optional `data`. michael@0: */ michael@0: resolve: function PDFObjects_resolve(objId, data) { michael@0: var obj = this.ensureObj(objId); michael@0: michael@0: obj.resolved = true; michael@0: obj.data = data; michael@0: obj.promise.resolve(data); michael@0: }, michael@0: michael@0: isResolved: function PDFObjects_isResolved(objId) { michael@0: var objs = this.objs; michael@0: michael@0: if (!objs[objId]) { michael@0: return false; michael@0: } else { michael@0: return objs[objId].resolved; michael@0: } michael@0: }, michael@0: michael@0: hasData: function PDFObjects_hasData(objId) { michael@0: return this.isResolved(objId); michael@0: }, michael@0: michael@0: /** michael@0: * Returns the data of `objId` if object exists, null otherwise. michael@0: */ michael@0: getData: function PDFObjects_getData(objId) { michael@0: var objs = this.objs; michael@0: if (!objs[objId] || !objs[objId].resolved) { michael@0: return null; michael@0: } else { michael@0: return objs[objId].data; michael@0: } michael@0: }, michael@0: michael@0: clear: function PDFObjects_clear() { michael@0: this.objs = {}; michael@0: } michael@0: }; michael@0: return PDFObjects; michael@0: })(); michael@0: michael@0: /** michael@0: * Allows controlling of the rendering tasks. michael@0: * @class michael@0: */ michael@0: var RenderTask = (function RenderTaskClosure() { michael@0: function RenderTask(internalRenderTask) { michael@0: this.internalRenderTask = internalRenderTask; michael@0: /** michael@0: * Promise for rendering task completion. michael@0: * @type {Promise} michael@0: */ michael@0: this.promise = new PDFJS.LegacyPromise(); michael@0: } michael@0: michael@0: RenderTask.prototype = /** @lends RenderTask.prototype */ { michael@0: /** michael@0: * Cancels the rendering task. If the task is currently rendering it will michael@0: * not be cancelled until graphics pauses with a timeout. The promise that michael@0: * this object extends will resolved when cancelled. michael@0: */ michael@0: cancel: function RenderTask_cancel() { michael@0: this.internalRenderTask.cancel(); michael@0: this.promise.reject(new Error('Rendering is cancelled')); michael@0: }, michael@0: michael@0: /** michael@0: * Registers callback to indicate the rendering task completion. michael@0: * michael@0: * @param {function} onFulfilled The callback for the rendering completion. michael@0: * @param {function} onRejected The callback for the rendering failure. michael@0: * @return {Promise} A promise that is resolved after the onFulfilled or michael@0: * onRejected callback. michael@0: */ michael@0: then: function RenderTask_then(onFulfilled, onRejected) { michael@0: return this.promise.then(onFulfilled, onRejected); michael@0: } michael@0: }; michael@0: michael@0: return RenderTask; michael@0: })(); michael@0: michael@0: /** michael@0: * For internal use only. michael@0: * @ignore michael@0: */ michael@0: var InternalRenderTask = (function InternalRenderTaskClosure() { michael@0: michael@0: function InternalRenderTask(callback, params, objs, commonObjs, operatorList, michael@0: pageNumber) { michael@0: this.callback = callback; michael@0: this.params = params; michael@0: this.objs = objs; michael@0: this.commonObjs = commonObjs; michael@0: this.operatorListIdx = null; michael@0: this.operatorList = operatorList; michael@0: this.pageNumber = pageNumber; michael@0: this.running = false; michael@0: this.graphicsReadyCallback = null; michael@0: this.graphicsReady = false; michael@0: this.cancelled = false; michael@0: } michael@0: michael@0: InternalRenderTask.prototype = { michael@0: michael@0: initalizeGraphics: michael@0: function InternalRenderTask_initalizeGraphics(transparency) { michael@0: michael@0: if (this.cancelled) { michael@0: return; michael@0: } michael@0: if (PDFJS.pdfBug && 'StepperManager' in globalScope && michael@0: globalScope.StepperManager.enabled) { michael@0: this.stepper = globalScope.StepperManager.create(this.pageNumber - 1); michael@0: this.stepper.init(this.operatorList); michael@0: this.stepper.nextBreakPoint = this.stepper.getNextBreakPoint(); michael@0: } michael@0: michael@0: var params = this.params; michael@0: this.gfx = new CanvasGraphics(params.canvasContext, this.commonObjs, michael@0: this.objs, params.imageLayer); michael@0: michael@0: this.gfx.beginDrawing(params.viewport, transparency); michael@0: this.operatorListIdx = 0; michael@0: this.graphicsReady = true; michael@0: if (this.graphicsReadyCallback) { michael@0: this.graphicsReadyCallback(); michael@0: } michael@0: }, michael@0: michael@0: cancel: function InternalRenderTask_cancel() { michael@0: this.running = false; michael@0: this.cancelled = true; michael@0: this.callback('cancelled'); michael@0: }, michael@0: michael@0: operatorListChanged: function InternalRenderTask_operatorListChanged() { michael@0: if (!this.graphicsReady) { michael@0: if (!this.graphicsReadyCallback) { michael@0: this.graphicsReadyCallback = this._continue.bind(this); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: if (this.stepper) { michael@0: this.stepper.updateOperatorList(this.operatorList); michael@0: } michael@0: michael@0: if (this.running) { michael@0: return; michael@0: } michael@0: this._continue(); michael@0: }, michael@0: michael@0: _continue: function InternalRenderTask__continue() { michael@0: this.running = true; michael@0: if (this.cancelled) { michael@0: return; michael@0: } michael@0: if (this.params.continueCallback) { michael@0: this.params.continueCallback(this._next.bind(this)); michael@0: } else { michael@0: this._next(); michael@0: } michael@0: }, michael@0: michael@0: _next: function InternalRenderTask__next() { michael@0: if (this.cancelled) { michael@0: return; michael@0: } michael@0: this.operatorListIdx = this.gfx.executeOperatorList(this.operatorList, michael@0: this.operatorListIdx, michael@0: this._continue.bind(this), michael@0: this.stepper); michael@0: if (this.operatorListIdx === this.operatorList.argsArray.length) { michael@0: this.running = false; michael@0: if (this.operatorList.lastChunk) { michael@0: this.gfx.endDrawing(); michael@0: this.callback(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: }; michael@0: michael@0: return InternalRenderTask; michael@0: })(); michael@0: michael@0: michael@0: var Metadata = PDFJS.Metadata = (function MetadataClosure() { michael@0: function fixMetadata(meta) { michael@0: return meta.replace(/>\\376\\377([^<]+)/g, function(all, codes) { michael@0: var bytes = codes.replace(/\\([0-3])([0-7])([0-7])/g, michael@0: function(code, d1, d2, d3) { michael@0: return String.fromCharCode(d1 * 64 + d2 * 8 + d3 * 1); michael@0: }); michael@0: var chars = ''; michael@0: for (var i = 0; i < bytes.length; i += 2) { michael@0: var code = bytes.charCodeAt(i) * 256 + bytes.charCodeAt(i + 1); michael@0: chars += code >= 32 && code < 127 && code != 60 && code != 62 && michael@0: code != 38 && false ? String.fromCharCode(code) : michael@0: '&#x' + (0x10000 + code).toString(16).substring(1) + ';'; michael@0: } michael@0: return '>' + chars; michael@0: }); michael@0: } michael@0: michael@0: function Metadata(meta) { michael@0: if (typeof meta === 'string') { michael@0: // Ghostscript produces invalid metadata michael@0: meta = fixMetadata(meta); michael@0: michael@0: var parser = new DOMParser(); michael@0: meta = parser.parseFromString(meta, 'application/xml'); michael@0: } else if (!(meta instanceof Document)) { michael@0: error('Metadata: Invalid metadata object'); michael@0: } michael@0: michael@0: this.metaDocument = meta; michael@0: this.metadata = {}; michael@0: this.parse(); michael@0: } michael@0: michael@0: Metadata.prototype = { michael@0: parse: function Metadata_parse() { michael@0: var doc = this.metaDocument; michael@0: var rdf = doc.documentElement; michael@0: michael@0: if (rdf.nodeName.toLowerCase() !== 'rdf:rdf') { // Wrapped in michael@0: rdf = rdf.firstChild; michael@0: while (rdf && rdf.nodeName.toLowerCase() !== 'rdf:rdf') { michael@0: rdf = rdf.nextSibling; michael@0: } michael@0: } michael@0: michael@0: var nodeName = (rdf) ? rdf.nodeName.toLowerCase() : null; michael@0: if (!rdf || nodeName !== 'rdf:rdf' || !rdf.hasChildNodes()) { michael@0: return; michael@0: } michael@0: michael@0: var children = rdf.childNodes, desc, entry, name, i, ii, length, iLength; michael@0: for (i = 0, length = children.length; i < length; i++) { michael@0: desc = children[i]; michael@0: if (desc.nodeName.toLowerCase() !== 'rdf:description') { michael@0: continue; michael@0: } michael@0: michael@0: for (ii = 0, iLength = desc.childNodes.length; ii < iLength; ii++) { michael@0: if (desc.childNodes[ii].nodeName.toLowerCase() !== '#text') { michael@0: entry = desc.childNodes[ii]; michael@0: name = entry.nodeName.toLowerCase(); michael@0: this.metadata[name] = entry.textContent.trim(); michael@0: } michael@0: } michael@0: } michael@0: }, michael@0: michael@0: get: function Metadata_get(name) { michael@0: return this.metadata[name] || null; michael@0: }, michael@0: michael@0: has: function Metadata_has(name) { michael@0: return typeof this.metadata[name] !== 'undefined'; michael@0: } michael@0: }; michael@0: michael@0: return Metadata; michael@0: })(); michael@0: michael@0: michael@0: // contexts store most of the state we need natively. michael@0: // However, PDF needs a bit more state, which we store here. michael@0: michael@0: // Minimal font size that would be used during canvas fillText operations. michael@0: var MIN_FONT_SIZE = 16; michael@0: var MAX_GROUP_SIZE = 4096; michael@0: michael@0: var COMPILE_TYPE3_GLYPHS = true; michael@0: michael@0: function createScratchCanvas(width, height) { michael@0: var canvas = document.createElement('canvas'); michael@0: canvas.width = width; michael@0: canvas.height = height; michael@0: return canvas; michael@0: } michael@0: michael@0: function addContextCurrentTransform(ctx) { michael@0: // If the context doesn't expose a `mozCurrentTransform`, add a JS based on. michael@0: if (!ctx.mozCurrentTransform) { michael@0: // Store the original context michael@0: ctx._scaleX = ctx._scaleX || 1.0; michael@0: ctx._scaleY = ctx._scaleY || 1.0; michael@0: ctx._originalSave = ctx.save; michael@0: ctx._originalRestore = ctx.restore; michael@0: ctx._originalRotate = ctx.rotate; michael@0: ctx._originalScale = ctx.scale; michael@0: ctx._originalTranslate = ctx.translate; michael@0: ctx._originalTransform = ctx.transform; michael@0: ctx._originalSetTransform = ctx.setTransform; michael@0: michael@0: ctx._transformMatrix = [ctx._scaleX, 0, 0, ctx._scaleY, 0, 0]; michael@0: ctx._transformStack = []; michael@0: michael@0: Object.defineProperty(ctx, 'mozCurrentTransform', { michael@0: get: function getCurrentTransform() { michael@0: return this._transformMatrix; michael@0: } michael@0: }); michael@0: michael@0: Object.defineProperty(ctx, 'mozCurrentTransformInverse', { michael@0: get: function getCurrentTransformInverse() { michael@0: // Calculation done using WolframAlpha: michael@0: // http://www.wolframalpha.com/input/? michael@0: // i=Inverse+{{a%2C+c%2C+e}%2C+{b%2C+d%2C+f}%2C+{0%2C+0%2C+1}} michael@0: michael@0: var m = this._transformMatrix; michael@0: var a = m[0], b = m[1], c = m[2], d = m[3], e = m[4], f = m[5]; michael@0: michael@0: var ad_bc = a * d - b * c; michael@0: var bc_ad = b * c - a * d; michael@0: michael@0: return [ michael@0: d / ad_bc, michael@0: b / bc_ad, michael@0: c / bc_ad, michael@0: a / ad_bc, michael@0: (d * e - c * f) / bc_ad, michael@0: (b * e - a * f) / ad_bc michael@0: ]; michael@0: } michael@0: }); michael@0: michael@0: ctx.save = function ctxSave() { michael@0: var old = this._transformMatrix; michael@0: this._transformStack.push(old); michael@0: this._transformMatrix = old.slice(0, 6); michael@0: michael@0: this._originalSave(); michael@0: }; michael@0: michael@0: ctx.restore = function ctxRestore() { michael@0: var prev = this._transformStack.pop(); michael@0: if (prev) { michael@0: this._transformMatrix = prev; michael@0: this._originalRestore(); michael@0: } michael@0: }; michael@0: michael@0: ctx.translate = function ctxTranslate(x, y) { michael@0: var m = this._transformMatrix; michael@0: m[4] = m[0] * x + m[2] * y + m[4]; michael@0: m[5] = m[1] * x + m[3] * y + m[5]; michael@0: michael@0: this._originalTranslate(x, y); michael@0: }; michael@0: michael@0: ctx.scale = function ctxScale(x, y) { michael@0: var m = this._transformMatrix; michael@0: m[0] = m[0] * x; michael@0: m[1] = m[1] * x; michael@0: m[2] = m[2] * y; michael@0: m[3] = m[3] * y; michael@0: michael@0: this._originalScale(x, y); michael@0: }; michael@0: michael@0: ctx.transform = function ctxTransform(a, b, c, d, e, f) { michael@0: var m = this._transformMatrix; michael@0: this._transformMatrix = [ michael@0: m[0] * a + m[2] * b, michael@0: m[1] * a + m[3] * b, michael@0: m[0] * c + m[2] * d, michael@0: m[1] * c + m[3] * d, michael@0: m[0] * e + m[2] * f + m[4], michael@0: m[1] * e + m[3] * f + m[5] michael@0: ]; michael@0: michael@0: ctx._originalTransform(a, b, c, d, e, f); michael@0: }; michael@0: michael@0: ctx.setTransform = function ctxSetTransform(a, b, c, d, e, f) { michael@0: this._transformMatrix = [a, b, c, d, e, f]; michael@0: michael@0: ctx._originalSetTransform(a, b, c, d, e, f); michael@0: }; michael@0: michael@0: ctx.rotate = function ctxRotate(angle) { michael@0: var cosValue = Math.cos(angle); michael@0: var sinValue = Math.sin(angle); michael@0: michael@0: var m = this._transformMatrix; michael@0: this._transformMatrix = [ michael@0: m[0] * cosValue + m[2] * sinValue, michael@0: m[1] * cosValue + m[3] * sinValue, michael@0: m[0] * (-sinValue) + m[2] * cosValue, michael@0: m[1] * (-sinValue) + m[3] * cosValue, michael@0: m[4], michael@0: m[5] michael@0: ]; michael@0: michael@0: this._originalRotate(angle); michael@0: }; michael@0: } michael@0: } michael@0: michael@0: var CachedCanvases = (function CachedCanvasesClosure() { michael@0: var cache = {}; michael@0: return { michael@0: getCanvas: function CachedCanvases_getCanvas(id, width, height, michael@0: trackTransform) { michael@0: var canvasEntry; michael@0: if (id in cache) { michael@0: canvasEntry = cache[id]; michael@0: canvasEntry.canvas.width = width; michael@0: canvasEntry.canvas.height = height; michael@0: // reset canvas transform for emulated mozCurrentTransform, if needed michael@0: canvasEntry.context.setTransform(1, 0, 0, 1, 0, 0); michael@0: } else { michael@0: var canvas = createScratchCanvas(width, height); michael@0: var ctx = canvas.getContext('2d'); michael@0: if (trackTransform) { michael@0: addContextCurrentTransform(ctx); michael@0: } michael@0: cache[id] = canvasEntry = {canvas: canvas, context: ctx}; michael@0: } michael@0: return canvasEntry; michael@0: }, michael@0: clear: function () { michael@0: cache = {}; michael@0: } michael@0: }; michael@0: })(); michael@0: michael@0: function compileType3Glyph(imgData) { michael@0: var POINT_TO_PROCESS_LIMIT = 1000; michael@0: michael@0: var width = imgData.width, height = imgData.height; michael@0: var i, j, j0, width1 = width + 1; michael@0: var points = new Uint8Array(width1 * (height + 1)); michael@0: var POINT_TYPES = michael@0: new Uint8Array([0, 2, 4, 0, 1, 0, 5, 4, 8, 10, 0, 8, 0, 2, 1, 0]); michael@0: michael@0: // decodes bit-packed mask data michael@0: var lineSize = (width + 7) & ~7, data0 = imgData.data; michael@0: var data = new Uint8Array(lineSize * height), pos = 0, ii; michael@0: for (i = 0, ii = data0.length; i < ii; i++) { michael@0: var mask = 128, elem = data0[i]; michael@0: while (mask > 0) { michael@0: data[pos++] = (elem & mask) ? 0 : 255; michael@0: mask >>= 1; michael@0: } michael@0: } michael@0: michael@0: // finding iteresting points: every point is located between mask pixels, michael@0: // so there will be points of the (width + 1)x(height + 1) grid. Every point michael@0: // will have flags assigned based on neighboring mask pixels: michael@0: // 4 | 8 michael@0: // --P-- michael@0: // 2 | 1 michael@0: // We are interested only in points with the flags: michael@0: // - outside corners: 1, 2, 4, 8; michael@0: // - inside corners: 7, 11, 13, 14; michael@0: // - and, intersections: 5, 10. michael@0: var count = 0; michael@0: pos = 0; michael@0: if (data[pos] !== 0) { michael@0: points[0] = 1; michael@0: ++count; michael@0: } michael@0: for (j = 1; j < width; j++) { michael@0: if (data[pos] !== data[pos + 1]) { michael@0: points[j] = data[pos] ? 2 : 1; michael@0: ++count; michael@0: } michael@0: pos++; michael@0: } michael@0: if (data[pos] !== 0) { michael@0: points[j] = 2; michael@0: ++count; michael@0: } michael@0: for (i = 1; i < height; i++) { michael@0: pos = i * lineSize; michael@0: j0 = i * width1; michael@0: if (data[pos - lineSize] !== data[pos]) { michael@0: points[j0] = data[pos] ? 1 : 8; michael@0: ++count; michael@0: } michael@0: // 'sum' is the position of the current pixel configuration in the 'TYPES' michael@0: // array (in order 8-1-2-4, so we can use '>>2' to shift the column). michael@0: var sum = (data[pos] ? 4 : 0) + (data[pos - lineSize] ? 8 : 0); michael@0: for (j = 1; j < width; j++) { michael@0: sum = (sum >> 2) + (data[pos + 1] ? 4 : 0) + michael@0: (data[pos - lineSize + 1] ? 8 : 0); michael@0: if (POINT_TYPES[sum]) { michael@0: points[j0 + j] = POINT_TYPES[sum]; michael@0: ++count; michael@0: } michael@0: pos++; michael@0: } michael@0: if (data[pos - lineSize] !== data[pos]) { michael@0: points[j0 + j] = data[pos] ? 2 : 4; michael@0: ++count; michael@0: } michael@0: michael@0: if (count > POINT_TO_PROCESS_LIMIT) { michael@0: return null; michael@0: } michael@0: } michael@0: michael@0: pos = lineSize * (height - 1); michael@0: j0 = i * width1; michael@0: if (data[pos] !== 0) { michael@0: points[j0] = 8; michael@0: ++count; michael@0: } michael@0: for (j = 1; j < width; j++) { michael@0: if (data[pos] !== data[pos + 1]) { michael@0: points[j0 + j] = data[pos] ? 4 : 8; michael@0: ++count; michael@0: } michael@0: pos++; michael@0: } michael@0: if (data[pos] !== 0) { michael@0: points[j0 + j] = 4; michael@0: ++count; michael@0: } michael@0: if (count > POINT_TO_PROCESS_LIMIT) { michael@0: return null; michael@0: } michael@0: michael@0: // building outlines michael@0: var steps = new Int32Array([0, width1, -1, 0, -width1, 0, 0, 0, 1]); michael@0: var outlines = []; michael@0: for (i = 0; count && i <= height; i++) { michael@0: var p = i * width1; michael@0: var end = p + width; michael@0: while (p < end && !points[p]) { michael@0: p++; michael@0: } michael@0: if (p === end) { michael@0: continue; michael@0: } michael@0: var coords = [p % width1, i]; michael@0: michael@0: var type = points[p], p0 = p, pp; michael@0: do { michael@0: var step = steps[type]; michael@0: do { michael@0: p += step; michael@0: } while (!points[p]); michael@0: michael@0: pp = points[p]; michael@0: if (pp !== 5 && pp !== 10) { michael@0: // set new direction michael@0: type = pp; michael@0: // delete mark michael@0: points[p] = 0; michael@0: } else { // type is 5 or 10, ie, a crossing michael@0: // set new direction michael@0: type = pp & ((0x33 * type) >> 4); michael@0: // set new type for "future hit" michael@0: points[p] &= (type >> 2 | type << 2); michael@0: } michael@0: michael@0: coords.push(p % width1); michael@0: coords.push((p / width1) | 0); michael@0: --count; michael@0: } while (p0 !== p); michael@0: outlines.push(coords); michael@0: --i; michael@0: } michael@0: michael@0: var drawOutline = function(c) { michael@0: c.save(); michael@0: // the path shall be painted in [0..1]x[0..1] space michael@0: c.scale(1 / width, -1 / height); michael@0: c.translate(0, -height); michael@0: c.beginPath(); michael@0: for (var i = 0, ii = outlines.length; i < ii; i++) { michael@0: var o = outlines[i]; michael@0: c.moveTo(o[0], o[1]); michael@0: for (var j = 2, jj = o.length; j < jj; j += 2) { michael@0: c.lineTo(o[j], o[j+1]); michael@0: } michael@0: } michael@0: c.fill(); michael@0: c.beginPath(); michael@0: c.restore(); michael@0: }; michael@0: michael@0: return drawOutline; michael@0: } michael@0: michael@0: var CanvasExtraState = (function CanvasExtraStateClosure() { michael@0: function CanvasExtraState(old) { michael@0: // Are soft masks and alpha values shapes or opacities? michael@0: this.alphaIsShape = false; michael@0: this.fontSize = 0; michael@0: this.fontSizeScale = 1; michael@0: this.textMatrix = IDENTITY_MATRIX; michael@0: this.fontMatrix = FONT_IDENTITY_MATRIX; michael@0: this.leading = 0; michael@0: // Current point (in user coordinates) michael@0: this.x = 0; michael@0: this.y = 0; michael@0: // Start of text line (in text coordinates) michael@0: this.lineX = 0; michael@0: this.lineY = 0; michael@0: // Character and word spacing michael@0: this.charSpacing = 0; michael@0: this.wordSpacing = 0; michael@0: this.textHScale = 1; michael@0: this.textRenderingMode = TextRenderingMode.FILL; michael@0: this.textRise = 0; michael@0: // Color spaces michael@0: this.fillColorSpace = ColorSpace.singletons.gray; michael@0: this.fillColorSpaceObj = null; michael@0: this.strokeColorSpace = ColorSpace.singletons.gray; michael@0: this.strokeColorSpaceObj = null; michael@0: this.fillColorObj = null; michael@0: this.strokeColorObj = null; michael@0: // Default fore and background colors michael@0: this.fillColor = '#000000'; michael@0: this.strokeColor = '#000000'; michael@0: // Note: fill alpha applies to all non-stroking operations michael@0: this.fillAlpha = 1; michael@0: this.strokeAlpha = 1; michael@0: this.lineWidth = 1; michael@0: this.activeSMask = null; // nonclonable field (see the save method below) michael@0: michael@0: this.old = old; michael@0: } michael@0: michael@0: CanvasExtraState.prototype = { michael@0: clone: function CanvasExtraState_clone() { michael@0: return Object.create(this); michael@0: }, michael@0: setCurrentPoint: function CanvasExtraState_setCurrentPoint(x, y) { michael@0: this.x = x; michael@0: this.y = y; michael@0: } michael@0: }; michael@0: return CanvasExtraState; michael@0: })(); michael@0: michael@0: var CanvasGraphics = (function CanvasGraphicsClosure() { michael@0: // Defines the time the executeOperatorList is going to be executing michael@0: // before it stops and shedules a continue of execution. michael@0: var EXECUTION_TIME = 15; michael@0: michael@0: function CanvasGraphics(canvasCtx, commonObjs, objs, imageLayer) { michael@0: this.ctx = canvasCtx; michael@0: this.current = new CanvasExtraState(); michael@0: this.stateStack = []; michael@0: this.pendingClip = null; michael@0: this.pendingEOFill = false; michael@0: this.res = null; michael@0: this.xobjs = null; michael@0: this.commonObjs = commonObjs; michael@0: this.objs = objs; michael@0: this.imageLayer = imageLayer; michael@0: this.groupStack = []; michael@0: this.processingType3 = null; michael@0: // Patterns are painted relative to the initial page/form transform, see pdf michael@0: // spec 8.7.2 NOTE 1. michael@0: this.baseTransform = null; michael@0: this.baseTransformStack = []; michael@0: this.groupLevel = 0; michael@0: this.smaskStack = []; michael@0: this.smaskCounter = 0; michael@0: this.tempSMask = null; michael@0: if (canvasCtx) { michael@0: addContextCurrentTransform(canvasCtx); michael@0: } michael@0: } michael@0: michael@0: function putBinaryImageData(ctx, imgData) { michael@0: if (typeof ImageData !== 'undefined' && imgData instanceof ImageData) { michael@0: ctx.putImageData(imgData, 0, 0); michael@0: return; michael@0: } michael@0: michael@0: // Put the image data to the canvas in chunks, rather than putting the michael@0: // whole image at once. This saves JS memory, because the ImageData object michael@0: // is smaller. It also possibly saves C++ memory within the implementation michael@0: // of putImageData(). (E.g. in Firefox we make two short-lived copies of michael@0: // the data passed to putImageData()). |n| shouldn't be too small, however, michael@0: // because too many putImageData() calls will slow things down. michael@0: // michael@0: // Note: as written, if the last chunk is partial, the putImageData() call michael@0: // will (conceptually) put pixels past the bounds of the canvas. But michael@0: // that's ok; any such pixels are ignored. michael@0: michael@0: var height = imgData.height, width = imgData.width; michael@0: var fullChunkHeight = 16; michael@0: var fracChunks = height / fullChunkHeight; michael@0: var fullChunks = Math.floor(fracChunks); michael@0: var totalChunks = Math.ceil(fracChunks); michael@0: var partialChunkHeight = height - fullChunks * fullChunkHeight; michael@0: michael@0: var chunkImgData = ctx.createImageData(width, fullChunkHeight); michael@0: var srcPos = 0, destPos; michael@0: var src = imgData.data; michael@0: var dest = chunkImgData.data; michael@0: var i, j, thisChunkHeight, elemsInThisChunk; michael@0: michael@0: // There are multiple forms in which the pixel data can be passed, and michael@0: // imgData.kind tells us which one this is. michael@0: if (imgData.kind === ImageKind.GRAYSCALE_1BPP) { michael@0: // Grayscale, 1 bit per pixel (i.e. black-and-white). michael@0: var srcLength = src.byteLength; michael@0: var dest32 = PDFJS.hasCanvasTypedArrays ? new Uint32Array(dest.buffer) : michael@0: new Uint32ArrayView(dest); michael@0: var dest32DataLength = dest32.length; michael@0: var fullSrcDiff = (width + 7) >> 3; michael@0: var white = 0xFFFFFFFF; michael@0: var black = (PDFJS.isLittleEndian || !PDFJS.hasCanvasTypedArrays) ? michael@0: 0xFF000000 : 0x000000FF; michael@0: for (i = 0; i < totalChunks; i++) { michael@0: thisChunkHeight = michael@0: (i < fullChunks) ? fullChunkHeight : partialChunkHeight; michael@0: destPos = 0; michael@0: for (j = 0; j < thisChunkHeight; j++) { michael@0: var srcDiff = srcLength - srcPos; michael@0: var k = 0; michael@0: var kEnd = (srcDiff > fullSrcDiff) ? width : srcDiff * 8 - 7; michael@0: var kEndUnrolled = kEnd & ~7; michael@0: var mask = 0; michael@0: var srcByte = 0; michael@0: for (; k < kEndUnrolled; k += 8) { michael@0: srcByte = src[srcPos++]; michael@0: dest32[destPos++] = (srcByte & 128) ? white : black; michael@0: dest32[destPos++] = (srcByte & 64) ? white : black; michael@0: dest32[destPos++] = (srcByte & 32) ? white : black; michael@0: dest32[destPos++] = (srcByte & 16) ? white : black; michael@0: dest32[destPos++] = (srcByte & 8) ? white : black; michael@0: dest32[destPos++] = (srcByte & 4) ? white : black; michael@0: dest32[destPos++] = (srcByte & 2) ? white : black; michael@0: dest32[destPos++] = (srcByte & 1) ? white : black; michael@0: } michael@0: for (; k < kEnd; k++) { michael@0: if (mask === 0) { michael@0: srcByte = src[srcPos++]; michael@0: mask = 128; michael@0: } michael@0: michael@0: dest32[destPos++] = (srcByte & mask) ? white : black; michael@0: mask >>= 1; michael@0: } michael@0: } michael@0: // We ran out of input. Make all remaining pixels transparent. michael@0: while (destPos < dest32DataLength) { michael@0: dest32[destPos++] = 0; michael@0: } michael@0: michael@0: ctx.putImageData(chunkImgData, 0, i * fullChunkHeight); michael@0: } michael@0: } else if (imgData.kind === ImageKind.RGBA_32BPP) { michael@0: // RGBA, 32-bits per pixel. michael@0: michael@0: for (i = 0; i < totalChunks; i++) { michael@0: thisChunkHeight = michael@0: (i < fullChunks) ? fullChunkHeight : partialChunkHeight; michael@0: elemsInThisChunk = imgData.width * thisChunkHeight * 4; michael@0: michael@0: dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk)); michael@0: srcPos += elemsInThisChunk; michael@0: michael@0: ctx.putImageData(chunkImgData, 0, i * fullChunkHeight); michael@0: } michael@0: } else if (imgData.kind === ImageKind.RGB_24BPP) { michael@0: // RGB, 24-bits per pixel. michael@0: for (i = 0; i < totalChunks; i++) { michael@0: thisChunkHeight = michael@0: (i < fullChunks) ? fullChunkHeight : partialChunkHeight; michael@0: elemsInThisChunk = imgData.width * thisChunkHeight * 3; michael@0: destPos = 0; michael@0: for (j = 0; j < elemsInThisChunk; j += 3) { michael@0: dest[destPos++] = src[srcPos++]; michael@0: dest[destPos++] = src[srcPos++]; michael@0: dest[destPos++] = src[srcPos++]; michael@0: dest[destPos++] = 255; michael@0: } michael@0: ctx.putImageData(chunkImgData, 0, i * fullChunkHeight); michael@0: } michael@0: } else { michael@0: error('bad image kind: ' + imgData.kind); michael@0: } michael@0: } michael@0: michael@0: function putBinaryImageMask(ctx, imgData) { michael@0: var height = imgData.height, width = imgData.width; michael@0: var fullChunkHeight = 16; michael@0: var fracChunks = height / fullChunkHeight; michael@0: var fullChunks = Math.floor(fracChunks); michael@0: var totalChunks = Math.ceil(fracChunks); michael@0: var partialChunkHeight = height - fullChunks * fullChunkHeight; michael@0: michael@0: var chunkImgData = ctx.createImageData(width, fullChunkHeight); michael@0: var srcPos = 0; michael@0: var src = imgData.data; michael@0: var dest = chunkImgData.data; michael@0: michael@0: for (var i = 0; i < totalChunks; i++) { michael@0: var thisChunkHeight = michael@0: (i < fullChunks) ? fullChunkHeight : partialChunkHeight; michael@0: michael@0: // Expand the mask so it can be used by the canvas. Any required michael@0: // inversion has already been handled. michael@0: var destPos = 3; // alpha component offset michael@0: for (var j = 0; j < thisChunkHeight; j++) { michael@0: var mask = 0; michael@0: for (var k = 0; k < width; k++) { michael@0: if (!mask) { michael@0: var elem = src[srcPos++]; michael@0: mask = 128; michael@0: } michael@0: dest[destPos] = (elem & mask) ? 0 : 255; michael@0: destPos += 4; michael@0: mask >>= 1; michael@0: } michael@0: } michael@0: ctx.putImageData(chunkImgData, 0, i * fullChunkHeight); michael@0: } michael@0: } michael@0: michael@0: function copyCtxState(sourceCtx, destCtx) { michael@0: var properties = ['strokeStyle', 'fillStyle', 'fillRule', 'globalAlpha', michael@0: 'lineWidth', 'lineCap', 'lineJoin', 'miterLimit', michael@0: 'globalCompositeOperation', 'font']; michael@0: for (var i = 0, ii = properties.length; i < ii; i++) { michael@0: var property = properties[i]; michael@0: if (property in sourceCtx) { michael@0: destCtx[property] = sourceCtx[property]; michael@0: } michael@0: } michael@0: if ('setLineDash' in sourceCtx) { michael@0: destCtx.setLineDash(sourceCtx.getLineDash()); michael@0: destCtx.lineDashOffset = sourceCtx.lineDashOffset; michael@0: } else if ('mozDash' in sourceCtx) { michael@0: destCtx.mozDash = sourceCtx.mozDash; michael@0: destCtx.mozDashOffset = sourceCtx.mozDashOffset; michael@0: } michael@0: } michael@0: michael@0: function genericComposeSMask(maskCtx, layerCtx, width, height, michael@0: subtype, backdrop) { michael@0: var addBackdropFn; michael@0: if (backdrop) { michael@0: addBackdropFn = function (r0, g0, b0, bytes) { michael@0: var length = bytes.length; michael@0: for (var i = 3; i < length; i += 4) { michael@0: var alpha = bytes[i] / 255; michael@0: if (alpha === 0) { michael@0: bytes[i - 3] = r0; michael@0: bytes[i - 2] = g0; michael@0: bytes[i - 1] = b0; michael@0: } else if (alpha < 1) { michael@0: var alpha_ = 1 - alpha; michael@0: bytes[i - 3] = (bytes[i - 3] * alpha + r0 * alpha_) | 0; michael@0: bytes[i - 2] = (bytes[i - 2] * alpha + g0 * alpha_) | 0; michael@0: bytes[i - 1] = (bytes[i - 1] * alpha + b0 * alpha_) | 0; michael@0: } michael@0: } michael@0: }.bind(null, backdrop[0], backdrop[1], backdrop[2]); michael@0: } else { michael@0: addBackdropFn = function () {}; michael@0: } michael@0: michael@0: var composeFn; michael@0: if (subtype === 'Luminosity') { michael@0: composeFn = function (maskDataBytes, layerDataBytes) { michael@0: var length = maskDataBytes.length; michael@0: for (var i = 3; i < length; i += 4) { michael@0: var y = ((maskDataBytes[i - 3] * 77) + // * 0.3 / 255 * 0x10000 michael@0: (maskDataBytes[i - 2] * 152) + // * 0.59 .... michael@0: (maskDataBytes[i - 1] * 28)) | 0; // * 0.11 .... michael@0: layerDataBytes[i] = (layerDataBytes[i] * y) >> 16; michael@0: } michael@0: }; michael@0: } else { michael@0: composeFn = function (maskDataBytes, layerDataBytes) { michael@0: var length = maskDataBytes.length; michael@0: for (var i = 3; i < length; i += 4) { michael@0: var alpha = maskDataBytes[i]; michael@0: layerDataBytes[i] = (layerDataBytes[i] * alpha / 255) | 0; michael@0: } michael@0: }; michael@0: } michael@0: michael@0: // processing image in chunks to save memory michael@0: var PIXELS_TO_PROCESS = 65536; michael@0: var chunkSize = Math.min(height, Math.ceil(PIXELS_TO_PROCESS / width)); michael@0: for (var row = 0; row < height; row += chunkSize) { michael@0: var chunkHeight = Math.min(chunkSize, height - row); michael@0: var maskData = maskCtx.getImageData(0, row, width, chunkHeight); michael@0: var layerData = layerCtx.getImageData(0, row, width, chunkHeight); michael@0: michael@0: addBackdropFn(maskData.data); michael@0: composeFn(maskData.data, layerData.data); michael@0: michael@0: maskCtx.putImageData(layerData, 0, row); michael@0: } michael@0: } michael@0: michael@0: function composeSMask(ctx, smask, layerCtx) { michael@0: var mask = smask.canvas; michael@0: var maskCtx = smask.context; michael@0: michael@0: ctx.setTransform(smask.scaleX, 0, 0, smask.scaleY, michael@0: smask.offsetX, smask.offsetY); michael@0: michael@0: var backdrop; michael@0: if (smask.backdrop) { michael@0: var cs = smask.colorSpace || ColorSpace.singletons.rgb; michael@0: backdrop = cs.getRgb(smask.backdrop, 0); michael@0: } michael@0: if (WebGLUtils.isEnabled) { michael@0: var composed = WebGLUtils.composeSMask(layerCtx.canvas, mask, michael@0: {subtype: smask.subtype, backdrop: backdrop}); michael@0: ctx.setTransform(1, 0, 0, 1, 0, 0); michael@0: ctx.drawImage(composed, smask.offsetX, smask.offsetY); michael@0: return; michael@0: } michael@0: genericComposeSMask(maskCtx, layerCtx, mask.width, mask.height, michael@0: smask.subtype, backdrop); michael@0: ctx.drawImage(mask, 0, 0); michael@0: } michael@0: michael@0: var LINE_CAP_STYLES = ['butt', 'round', 'square']; michael@0: var LINE_JOIN_STYLES = ['miter', 'round', 'bevel']; michael@0: var NORMAL_CLIP = {}; michael@0: var EO_CLIP = {}; michael@0: michael@0: CanvasGraphics.prototype = { michael@0: michael@0: beginDrawing: function CanvasGraphics_beginDrawing(viewport, transparency) { michael@0: // For pdfs that use blend modes we have to clear the canvas else certain michael@0: // blend modes can look wrong since we'd be blending with a white michael@0: // backdrop. The problem with a transparent backdrop though is we then michael@0: // don't get sub pixel anti aliasing on text, so we fill with white if michael@0: // we can. michael@0: var width = this.ctx.canvas.width; michael@0: var height = this.ctx.canvas.height; michael@0: if (transparency) { michael@0: this.ctx.clearRect(0, 0, width, height); michael@0: } else { michael@0: this.ctx.mozOpaque = true; michael@0: this.ctx.save(); michael@0: this.ctx.fillStyle = 'rgb(255, 255, 255)'; michael@0: this.ctx.fillRect(0, 0, width, height); michael@0: this.ctx.restore(); michael@0: } michael@0: michael@0: var transform = viewport.transform; michael@0: michael@0: this.ctx.save(); michael@0: this.ctx.transform.apply(this.ctx, transform); michael@0: michael@0: this.baseTransform = this.ctx.mozCurrentTransform.slice(); michael@0: michael@0: if (this.imageLayer) { michael@0: this.imageLayer.beginLayout(); michael@0: } michael@0: }, michael@0: michael@0: executeOperatorList: function CanvasGraphics_executeOperatorList( michael@0: operatorList, michael@0: executionStartIdx, continueCallback, michael@0: stepper) { michael@0: var argsArray = operatorList.argsArray; michael@0: var fnArray = operatorList.fnArray; michael@0: var i = executionStartIdx || 0; michael@0: var argsArrayLen = argsArray.length; michael@0: michael@0: // Sometimes the OperatorList to execute is empty. michael@0: if (argsArrayLen == i) { michael@0: return i; michael@0: } michael@0: michael@0: var endTime = Date.now() + EXECUTION_TIME; michael@0: michael@0: var commonObjs = this.commonObjs; michael@0: var objs = this.objs; michael@0: var fnId; michael@0: var deferred = Promise.resolve(); michael@0: michael@0: while (true) { michael@0: if (stepper && i === stepper.nextBreakPoint) { michael@0: stepper.breakIt(i, continueCallback); michael@0: return i; michael@0: } michael@0: michael@0: fnId = fnArray[i]; michael@0: michael@0: if (fnId !== OPS.dependency) { michael@0: this[fnId].apply(this, argsArray[i]); michael@0: } else { michael@0: var deps = argsArray[i]; michael@0: for (var n = 0, nn = deps.length; n < nn; n++) { michael@0: var depObjId = deps[n]; michael@0: var common = depObjId.substring(0, 2) == 'g_'; michael@0: michael@0: // If the promise isn't resolved yet, add the continueCallback michael@0: // to the promise and bail out. michael@0: if (!common && !objs.isResolved(depObjId)) { michael@0: objs.get(depObjId, continueCallback); michael@0: return i; michael@0: } michael@0: if (common && !commonObjs.isResolved(depObjId)) { michael@0: commonObjs.get(depObjId, continueCallback); michael@0: return i; michael@0: } michael@0: } michael@0: } michael@0: michael@0: i++; michael@0: michael@0: // If the entire operatorList was executed, stop as were done. michael@0: if (i == argsArrayLen) { michael@0: return i; michael@0: } michael@0: michael@0: // If the execution took longer then a certain amount of time, schedule michael@0: // to continue exeution after a short delay. michael@0: // However, this is only possible if a 'continueCallback' is passed in. michael@0: if (continueCallback && Date.now() > endTime) { michael@0: deferred.then(continueCallback); michael@0: return i; michael@0: } michael@0: michael@0: // If the operatorList isn't executed completely yet OR the execution michael@0: // time was short enough, do another execution round. michael@0: } michael@0: }, michael@0: michael@0: endDrawing: function CanvasGraphics_endDrawing() { michael@0: this.ctx.restore(); michael@0: CachedCanvases.clear(); michael@0: WebGLUtils.clear(); michael@0: michael@0: if (this.imageLayer) { michael@0: this.imageLayer.endLayout(); michael@0: } michael@0: }, michael@0: michael@0: // Graphics state michael@0: setLineWidth: function CanvasGraphics_setLineWidth(width) { michael@0: this.current.lineWidth = width; michael@0: this.ctx.lineWidth = width; michael@0: }, michael@0: setLineCap: function CanvasGraphics_setLineCap(style) { michael@0: this.ctx.lineCap = LINE_CAP_STYLES[style]; michael@0: }, michael@0: setLineJoin: function CanvasGraphics_setLineJoin(style) { michael@0: this.ctx.lineJoin = LINE_JOIN_STYLES[style]; michael@0: }, michael@0: setMiterLimit: function CanvasGraphics_setMiterLimit(limit) { michael@0: this.ctx.miterLimit = limit; michael@0: }, michael@0: setDash: function CanvasGraphics_setDash(dashArray, dashPhase) { michael@0: var ctx = this.ctx; michael@0: if ('setLineDash' in ctx) { michael@0: ctx.setLineDash(dashArray); michael@0: ctx.lineDashOffset = dashPhase; michael@0: } else { michael@0: ctx.mozDash = dashArray; michael@0: ctx.mozDashOffset = dashPhase; michael@0: } michael@0: }, michael@0: setRenderingIntent: function CanvasGraphics_setRenderingIntent(intent) { michael@0: // Maybe if we one day fully support color spaces this will be important michael@0: // for now we can ignore. michael@0: // TODO set rendering intent? michael@0: }, michael@0: setFlatness: function CanvasGraphics_setFlatness(flatness) { michael@0: // There's no way to control this with canvas, but we can safely ignore. michael@0: // TODO set flatness? michael@0: }, michael@0: setGState: function CanvasGraphics_setGState(states) { michael@0: for (var i = 0, ii = states.length; i < ii; i++) { michael@0: var state = states[i]; michael@0: var key = state[0]; michael@0: var value = state[1]; michael@0: michael@0: switch (key) { michael@0: case 'LW': michael@0: this.setLineWidth(value); michael@0: break; michael@0: case 'LC': michael@0: this.setLineCap(value); michael@0: break; michael@0: case 'LJ': michael@0: this.setLineJoin(value); michael@0: break; michael@0: case 'ML': michael@0: this.setMiterLimit(value); michael@0: break; michael@0: case 'D': michael@0: this.setDash(value[0], value[1]); michael@0: break; michael@0: case 'RI': michael@0: this.setRenderingIntent(value); michael@0: break; michael@0: case 'FL': michael@0: this.setFlatness(value); michael@0: break; michael@0: case 'Font': michael@0: this.setFont(value[0], value[1]); michael@0: break; michael@0: case 'CA': michael@0: this.current.strokeAlpha = state[1]; michael@0: break; michael@0: case 'ca': michael@0: this.current.fillAlpha = state[1]; michael@0: this.ctx.globalAlpha = state[1]; michael@0: break; michael@0: case 'BM': michael@0: if (value && value.name && (value.name !== 'Normal')) { michael@0: var mode = value.name.replace(/([A-Z])/g, michael@0: function(c) { michael@0: return '-' + c.toLowerCase(); michael@0: } michael@0: ).substring(1); michael@0: this.ctx.globalCompositeOperation = mode; michael@0: if (this.ctx.globalCompositeOperation !== mode) { michael@0: warn('globalCompositeOperation "' + mode + michael@0: '" is not supported'); michael@0: } michael@0: } else { michael@0: this.ctx.globalCompositeOperation = 'source-over'; michael@0: } michael@0: break; michael@0: case 'SMask': michael@0: if (this.current.activeSMask) { michael@0: this.endSMaskGroup(); michael@0: } michael@0: this.current.activeSMask = value ? this.tempSMask : null; michael@0: if (this.current.activeSMask) { michael@0: this.beginSMaskGroup(); michael@0: } michael@0: this.tempSMask = null; michael@0: break; michael@0: } michael@0: } michael@0: }, michael@0: beginSMaskGroup: function CanvasGraphics_beginSMaskGroup() { michael@0: michael@0: var activeSMask = this.current.activeSMask; michael@0: var drawnWidth = activeSMask.canvas.width; michael@0: var drawnHeight = activeSMask.canvas.height; michael@0: var cacheId = 'smaskGroupAt' + this.groupLevel; michael@0: var scratchCanvas = CachedCanvases.getCanvas( michael@0: cacheId, drawnWidth, drawnHeight, true); michael@0: michael@0: var currentCtx = this.ctx; michael@0: var currentTransform = currentCtx.mozCurrentTransform; michael@0: this.ctx.save(); michael@0: michael@0: var groupCtx = scratchCanvas.context; michael@0: groupCtx.scale(1 / activeSMask.scaleX, 1 / activeSMask.scaleY); michael@0: groupCtx.translate(-activeSMask.offsetX, -activeSMask.offsetY); michael@0: groupCtx.transform.apply(groupCtx, currentTransform); michael@0: michael@0: copyCtxState(currentCtx, groupCtx); michael@0: this.ctx = groupCtx; michael@0: this.setGState([ michael@0: ['BM', 'Normal'], michael@0: ['ca', 1], michael@0: ['CA', 1] michael@0: ]); michael@0: this.groupStack.push(currentCtx); michael@0: this.groupLevel++; michael@0: }, michael@0: endSMaskGroup: function CanvasGraphics_endSMaskGroup() { michael@0: var groupCtx = this.ctx; michael@0: this.groupLevel--; michael@0: this.ctx = this.groupStack.pop(); michael@0: michael@0: composeSMask(this.ctx, this.current.activeSMask, groupCtx); michael@0: this.ctx.restore(); michael@0: }, michael@0: save: function CanvasGraphics_save() { michael@0: this.ctx.save(); michael@0: var old = this.current; michael@0: this.stateStack.push(old); michael@0: this.current = old.clone(); michael@0: if (this.current.activeSMask) { michael@0: this.current.activeSMask = null; michael@0: } michael@0: }, michael@0: restore: function CanvasGraphics_restore() { michael@0: var prev = this.stateStack.pop(); michael@0: if (prev) { michael@0: if (this.current.activeSMask) { michael@0: this.endSMaskGroup(); michael@0: } michael@0: michael@0: this.current = prev; michael@0: this.ctx.restore(); michael@0: } michael@0: }, michael@0: transform: function CanvasGraphics_transform(a, b, c, d, e, f) { michael@0: this.ctx.transform(a, b, c, d, e, f); michael@0: }, michael@0: michael@0: // Path michael@0: moveTo: function CanvasGraphics_moveTo(x, y) { michael@0: this.ctx.moveTo(x, y); michael@0: this.current.setCurrentPoint(x, y); michael@0: }, michael@0: lineTo: function CanvasGraphics_lineTo(x, y) { michael@0: this.ctx.lineTo(x, y); michael@0: this.current.setCurrentPoint(x, y); michael@0: }, michael@0: curveTo: function CanvasGraphics_curveTo(x1, y1, x2, y2, x3, y3) { michael@0: this.ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3); michael@0: this.current.setCurrentPoint(x3, y3); michael@0: }, michael@0: curveTo2: function CanvasGraphics_curveTo2(x2, y2, x3, y3) { michael@0: var current = this.current; michael@0: this.ctx.bezierCurveTo(current.x, current.y, x2, y2, x3, y3); michael@0: current.setCurrentPoint(x3, y3); michael@0: }, michael@0: curveTo3: function CanvasGraphics_curveTo3(x1, y1, x3, y3) { michael@0: this.curveTo(x1, y1, x3, y3, x3, y3); michael@0: this.current.setCurrentPoint(x3, y3); michael@0: }, michael@0: closePath: function CanvasGraphics_closePath() { michael@0: this.ctx.closePath(); michael@0: }, michael@0: rectangle: function CanvasGraphics_rectangle(x, y, width, height) { michael@0: if (width === 0) { michael@0: width = this.getSinglePixelWidth(); michael@0: } michael@0: if (height === 0) { michael@0: height = this.getSinglePixelWidth(); michael@0: } michael@0: michael@0: this.ctx.rect(x, y, width, height); michael@0: }, michael@0: stroke: function CanvasGraphics_stroke(consumePath) { michael@0: consumePath = typeof consumePath !== 'undefined' ? consumePath : true; michael@0: var ctx = this.ctx; michael@0: var strokeColor = this.current.strokeColor; michael@0: if (this.current.lineWidth === 0) { michael@0: ctx.lineWidth = this.getSinglePixelWidth(); michael@0: } michael@0: // For stroke we want to temporarily change the global alpha to the michael@0: // stroking alpha. michael@0: ctx.globalAlpha = this.current.strokeAlpha; michael@0: if (strokeColor && strokeColor.hasOwnProperty('type') && michael@0: strokeColor.type === 'Pattern') { michael@0: // for patterns, we transform to pattern space, calculate michael@0: // the pattern, call stroke, and restore to user space michael@0: ctx.save(); michael@0: ctx.strokeStyle = strokeColor.getPattern(ctx, this); michael@0: ctx.stroke(); michael@0: ctx.restore(); michael@0: } else { michael@0: ctx.stroke(); michael@0: } michael@0: if (consumePath) { michael@0: this.consumePath(); michael@0: } michael@0: // Restore the global alpha to the fill alpha michael@0: ctx.globalAlpha = this.current.fillAlpha; michael@0: }, michael@0: closeStroke: function CanvasGraphics_closeStroke() { michael@0: this.closePath(); michael@0: this.stroke(); michael@0: }, michael@0: fill: function CanvasGraphics_fill(consumePath) { michael@0: consumePath = typeof consumePath !== 'undefined' ? consumePath : true; michael@0: var ctx = this.ctx; michael@0: var fillColor = this.current.fillColor; michael@0: var needRestore = false; michael@0: michael@0: if (fillColor && fillColor.hasOwnProperty('type') && michael@0: fillColor.type === 'Pattern') { michael@0: ctx.save(); michael@0: ctx.fillStyle = fillColor.getPattern(ctx, this); michael@0: needRestore = true; michael@0: } michael@0: michael@0: if (this.pendingEOFill) { michael@0: if ('mozFillRule' in this.ctx) { michael@0: this.ctx.mozFillRule = 'evenodd'; michael@0: this.ctx.fill(); michael@0: this.ctx.mozFillRule = 'nonzero'; michael@0: } else { michael@0: try { michael@0: this.ctx.fill('evenodd'); michael@0: } catch (ex) { michael@0: // shouldn't really happen, but browsers might think differently michael@0: this.ctx.fill(); michael@0: } michael@0: } michael@0: this.pendingEOFill = false; michael@0: } else { michael@0: this.ctx.fill(); michael@0: } michael@0: michael@0: if (needRestore) { michael@0: ctx.restore(); michael@0: } michael@0: if (consumePath) { michael@0: this.consumePath(); michael@0: } michael@0: }, michael@0: eoFill: function CanvasGraphics_eoFill() { michael@0: this.pendingEOFill = true; michael@0: this.fill(); michael@0: }, michael@0: fillStroke: function CanvasGraphics_fillStroke() { michael@0: this.fill(false); michael@0: this.stroke(false); michael@0: michael@0: this.consumePath(); michael@0: }, michael@0: eoFillStroke: function CanvasGraphics_eoFillStroke() { michael@0: this.pendingEOFill = true; michael@0: this.fillStroke(); michael@0: }, michael@0: closeFillStroke: function CanvasGraphics_closeFillStroke() { michael@0: this.closePath(); michael@0: this.fillStroke(); michael@0: }, michael@0: closeEOFillStroke: function CanvasGraphics_closeEOFillStroke() { michael@0: this.pendingEOFill = true; michael@0: this.closePath(); michael@0: this.fillStroke(); michael@0: }, michael@0: endPath: function CanvasGraphics_endPath() { michael@0: this.consumePath(); michael@0: }, michael@0: michael@0: // Clipping michael@0: clip: function CanvasGraphics_clip() { michael@0: this.pendingClip = NORMAL_CLIP; michael@0: }, michael@0: eoClip: function CanvasGraphics_eoClip() { michael@0: this.pendingClip = EO_CLIP; michael@0: }, michael@0: michael@0: // Text michael@0: beginText: function CanvasGraphics_beginText() { michael@0: this.current.textMatrix = IDENTITY_MATRIX; michael@0: this.current.x = this.current.lineX = 0; michael@0: this.current.y = this.current.lineY = 0; michael@0: }, michael@0: endText: function CanvasGraphics_endText() { michael@0: if (!('pendingTextPaths' in this)) { michael@0: this.ctx.beginPath(); michael@0: return; michael@0: } michael@0: var paths = this.pendingTextPaths; michael@0: var ctx = this.ctx; michael@0: michael@0: ctx.save(); michael@0: ctx.beginPath(); michael@0: for (var i = 0; i < paths.length; i++) { michael@0: var path = paths[i]; michael@0: ctx.setTransform.apply(ctx, path.transform); michael@0: ctx.translate(path.x, path.y); michael@0: path.addToPath(ctx, path.fontSize); michael@0: } michael@0: ctx.restore(); michael@0: ctx.clip(); michael@0: ctx.beginPath(); michael@0: delete this.pendingTextPaths; michael@0: }, michael@0: setCharSpacing: function CanvasGraphics_setCharSpacing(spacing) { michael@0: this.current.charSpacing = spacing; michael@0: }, michael@0: setWordSpacing: function CanvasGraphics_setWordSpacing(spacing) { michael@0: this.current.wordSpacing = spacing; michael@0: }, michael@0: setHScale: function CanvasGraphics_setHScale(scale) { michael@0: this.current.textHScale = scale / 100; michael@0: }, michael@0: setLeading: function CanvasGraphics_setLeading(leading) { michael@0: this.current.leading = -leading; michael@0: }, michael@0: setFont: function CanvasGraphics_setFont(fontRefName, size) { michael@0: var fontObj = this.commonObjs.get(fontRefName); michael@0: var current = this.current; michael@0: michael@0: if (!fontObj) { michael@0: error('Can\'t find font for ' + fontRefName); michael@0: } michael@0: michael@0: current.fontMatrix = (fontObj.fontMatrix ? michael@0: fontObj.fontMatrix : FONT_IDENTITY_MATRIX); michael@0: michael@0: // A valid matrix needs all main diagonal elements to be non-zero michael@0: // This also ensures we bypass FF bugzilla bug #719844. michael@0: if (current.fontMatrix[0] === 0 || michael@0: current.fontMatrix[3] === 0) { michael@0: warn('Invalid font matrix for font ' + fontRefName); michael@0: } michael@0: michael@0: // The spec for Tf (setFont) says that 'size' specifies the font 'scale', michael@0: // and in some docs this can be negative (inverted x-y axes). michael@0: if (size < 0) { michael@0: size = -size; michael@0: current.fontDirection = -1; michael@0: } else { michael@0: current.fontDirection = 1; michael@0: } michael@0: michael@0: this.current.font = fontObj; michael@0: this.current.fontSize = size; michael@0: michael@0: if (fontObj.coded) { michael@0: return; // we don't need ctx.font for Type3 fonts michael@0: } michael@0: michael@0: var name = fontObj.loadedName || 'sans-serif'; michael@0: var bold = fontObj.black ? (fontObj.bold ? 'bolder' : 'bold') : michael@0: (fontObj.bold ? 'bold' : 'normal'); michael@0: michael@0: var italic = fontObj.italic ? 'italic' : 'normal'; michael@0: var typeface = '"' + name + '", ' + fontObj.fallbackName; michael@0: michael@0: // Some font backends cannot handle fonts below certain size. michael@0: // Keeping the font at minimal size and using the fontSizeScale to change michael@0: // the current transformation matrix before the fillText/strokeText. michael@0: // See https://bugzilla.mozilla.org/show_bug.cgi?id=726227 michael@0: var browserFontSize = size >= MIN_FONT_SIZE ? size : MIN_FONT_SIZE; michael@0: this.current.fontSizeScale = browserFontSize != MIN_FONT_SIZE ? 1.0 : michael@0: size / MIN_FONT_SIZE; michael@0: michael@0: var rule = italic + ' ' + bold + ' ' + browserFontSize + 'px ' + typeface; michael@0: this.ctx.font = rule; michael@0: }, michael@0: setTextRenderingMode: function CanvasGraphics_setTextRenderingMode(mode) { michael@0: this.current.textRenderingMode = mode; michael@0: }, michael@0: setTextRise: function CanvasGraphics_setTextRise(rise) { michael@0: this.current.textRise = rise; michael@0: }, michael@0: moveText: function CanvasGraphics_moveText(x, y) { michael@0: this.current.x = this.current.lineX += x; michael@0: this.current.y = this.current.lineY += y; michael@0: }, michael@0: setLeadingMoveText: function CanvasGraphics_setLeadingMoveText(x, y) { michael@0: this.setLeading(-y); michael@0: this.moveText(x, y); michael@0: }, michael@0: setTextMatrix: function CanvasGraphics_setTextMatrix(a, b, c, d, e, f) { michael@0: this.current.textMatrix = [a, b, c, d, e, f]; michael@0: michael@0: this.current.x = this.current.lineX = 0; michael@0: this.current.y = this.current.lineY = 0; michael@0: }, michael@0: nextLine: function CanvasGraphics_nextLine() { michael@0: this.moveText(0, this.current.leading); michael@0: }, michael@0: applyTextTransforms: function CanvasGraphics_applyTextTransforms() { michael@0: var ctx = this.ctx; michael@0: var current = this.current; michael@0: ctx.transform.apply(ctx, current.textMatrix); michael@0: ctx.translate(current.x, current.y + current.textRise); michael@0: if (current.fontDirection > 0) { michael@0: ctx.scale(current.textHScale, -1); michael@0: } else { michael@0: ctx.scale(-current.textHScale, 1); michael@0: } michael@0: }, michael@0: michael@0: paintChar: function (character, x, y) { michael@0: var ctx = this.ctx; michael@0: var current = this.current; michael@0: var font = current.font; michael@0: var fontSize = current.fontSize / current.fontSizeScale; michael@0: var textRenderingMode = current.textRenderingMode; michael@0: var fillStrokeMode = textRenderingMode & michael@0: TextRenderingMode.FILL_STROKE_MASK; michael@0: var isAddToPathSet = !!(textRenderingMode & michael@0: TextRenderingMode.ADD_TO_PATH_FLAG); michael@0: michael@0: var addToPath; michael@0: if (font.disableFontFace || isAddToPathSet) { michael@0: addToPath = font.getPathGenerator(this.commonObjs, character); michael@0: } michael@0: michael@0: if (font.disableFontFace) { michael@0: ctx.save(); michael@0: ctx.translate(x, y); michael@0: ctx.beginPath(); michael@0: addToPath(ctx, fontSize); michael@0: if (fillStrokeMode === TextRenderingMode.FILL || michael@0: fillStrokeMode === TextRenderingMode.FILL_STROKE) { michael@0: ctx.fill(); michael@0: } michael@0: if (fillStrokeMode === TextRenderingMode.STROKE || michael@0: fillStrokeMode === TextRenderingMode.FILL_STROKE) { michael@0: ctx.stroke(); michael@0: } michael@0: ctx.restore(); michael@0: } else { michael@0: if (fillStrokeMode === TextRenderingMode.FILL || michael@0: fillStrokeMode === TextRenderingMode.FILL_STROKE) { michael@0: ctx.fillText(character, x, y); michael@0: } michael@0: if (fillStrokeMode === TextRenderingMode.STROKE || michael@0: fillStrokeMode === TextRenderingMode.FILL_STROKE) { michael@0: ctx.strokeText(character, x, y); michael@0: } michael@0: } michael@0: michael@0: if (isAddToPathSet) { michael@0: var paths = this.pendingTextPaths || (this.pendingTextPaths = []); michael@0: paths.push({ michael@0: transform: ctx.mozCurrentTransform, michael@0: x: x, michael@0: y: y, michael@0: fontSize: fontSize, michael@0: addToPath: addToPath michael@0: }); michael@0: } michael@0: }, michael@0: michael@0: get isFontSubpixelAAEnabled() { michael@0: // Checks if anti-aliasing is enabled when scaled text is painted. michael@0: // On Windows GDI scaled fonts looks bad. michael@0: var ctx = document.createElement('canvas').getContext('2d'); michael@0: ctx.scale(1.5, 1); michael@0: ctx.fillText('I', 0, 10); michael@0: var data = ctx.getImageData(0, 0, 10, 10).data; michael@0: var enabled = false; michael@0: for (var i = 3; i < data.length; i += 4) { michael@0: if (data[i] > 0 && data[i] < 255) { michael@0: enabled = true; michael@0: break; michael@0: } michael@0: } michael@0: return shadow(this, 'isFontSubpixelAAEnabled', enabled); michael@0: }, michael@0: michael@0: showText: function CanvasGraphics_showText(glyphs) { michael@0: var ctx = this.ctx; michael@0: var current = this.current; michael@0: var font = current.font; michael@0: var fontSize = current.fontSize; michael@0: var fontSizeScale = current.fontSizeScale; michael@0: var charSpacing = current.charSpacing; michael@0: var wordSpacing = current.wordSpacing; michael@0: var textHScale = current.textHScale * current.fontDirection; michael@0: var fontMatrix = current.fontMatrix || FONT_IDENTITY_MATRIX; michael@0: var glyphsLength = glyphs.length; michael@0: var vertical = font.vertical; michael@0: var defaultVMetrics = font.defaultVMetrics; michael@0: var i, glyph, width; michael@0: michael@0: if (fontSize === 0) { michael@0: return; michael@0: } michael@0: michael@0: // Type3 fonts - each glyph is a "mini-PDF" michael@0: if (font.coded) { michael@0: ctx.save(); michael@0: ctx.transform.apply(ctx, current.textMatrix); michael@0: ctx.translate(current.x, current.y); michael@0: michael@0: ctx.scale(textHScale, 1); michael@0: michael@0: for (i = 0; i < glyphsLength; ++i) { michael@0: glyph = glyphs[i]; michael@0: if (glyph === null) { michael@0: // word break michael@0: this.ctx.translate(wordSpacing, 0); michael@0: current.x += wordSpacing * textHScale; michael@0: continue; michael@0: } michael@0: michael@0: this.processingType3 = glyph; michael@0: this.save(); michael@0: ctx.scale(fontSize, fontSize); michael@0: ctx.transform.apply(ctx, fontMatrix); michael@0: this.executeOperatorList(glyph.operatorList); michael@0: this.restore(); michael@0: michael@0: var transformed = Util.applyTransform([glyph.width, 0], fontMatrix); michael@0: width = ((transformed[0] * fontSize + charSpacing) * michael@0: current.fontDirection); michael@0: michael@0: ctx.translate(width, 0); michael@0: current.x += width * textHScale; michael@0: } michael@0: ctx.restore(); michael@0: this.processingType3 = null; michael@0: } else { michael@0: ctx.save(); michael@0: this.applyTextTransforms(); michael@0: michael@0: var lineWidth = current.lineWidth; michael@0: var a1 = current.textMatrix[0], b1 = current.textMatrix[1]; michael@0: var scale = Math.sqrt(a1 * a1 + b1 * b1); michael@0: if (scale === 0 || lineWidth === 0) { michael@0: lineWidth = this.getSinglePixelWidth(); michael@0: } else { michael@0: lineWidth /= scale; michael@0: } michael@0: michael@0: if (fontSizeScale != 1.0) { michael@0: ctx.scale(fontSizeScale, fontSizeScale); michael@0: lineWidth /= fontSizeScale; michael@0: } michael@0: michael@0: ctx.lineWidth = lineWidth; michael@0: michael@0: var x = 0; michael@0: for (i = 0; i < glyphsLength; ++i) { michael@0: glyph = glyphs[i]; michael@0: if (glyph === null) { michael@0: // word break michael@0: x += current.fontDirection * wordSpacing; michael@0: continue; michael@0: } michael@0: michael@0: var restoreNeeded = false; michael@0: var character = glyph.fontChar; michael@0: var vmetric = glyph.vmetric || defaultVMetrics; michael@0: if (vertical) { michael@0: var vx = glyph.vmetric ? vmetric[1] : glyph.width * 0.5; michael@0: vx = -vx * fontSize * current.fontMatrix[0]; michael@0: var vy = vmetric[2] * fontSize * current.fontMatrix[0]; michael@0: } michael@0: width = vmetric ? -vmetric[0] : glyph.width; michael@0: var charWidth = width * fontSize * current.fontMatrix[0] + michael@0: charSpacing * current.fontDirection; michael@0: var accent = glyph.accent; michael@0: michael@0: var scaledX, scaledY, scaledAccentX, scaledAccentY; michael@0: michael@0: if (vertical) { michael@0: scaledX = vx / fontSizeScale; michael@0: scaledY = (x + vy) / fontSizeScale; michael@0: } else { michael@0: scaledX = x / fontSizeScale; michael@0: scaledY = 0; michael@0: } michael@0: michael@0: if (font.remeasure && width > 0 && this.isFontSubpixelAAEnabled) { michael@0: // some standard fonts may not have the exact width, trying to michael@0: // rescale per character michael@0: var measuredWidth = ctx.measureText(character).width * 1000 / michael@0: current.fontSize * current.fontSizeScale; michael@0: var characterScaleX = width / measuredWidth; michael@0: restoreNeeded = true; michael@0: ctx.save(); michael@0: ctx.scale(characterScaleX, 1); michael@0: scaledX /= characterScaleX; michael@0: if (accent) { michael@0: scaledAccentX /= characterScaleX; michael@0: } michael@0: } michael@0: michael@0: this.paintChar(character, scaledX, scaledY); michael@0: if (accent) { michael@0: scaledAccentX = scaledX + accent.offset.x / fontSizeScale; michael@0: scaledAccentY = scaledY - accent.offset.y / fontSizeScale; michael@0: this.paintChar(accent.fontChar, scaledAccentX, scaledAccentY); michael@0: } michael@0: michael@0: x += charWidth; michael@0: michael@0: if (restoreNeeded) { michael@0: ctx.restore(); michael@0: } michael@0: } michael@0: if (vertical) { michael@0: current.y -= x * textHScale; michael@0: } else { michael@0: current.x += x * textHScale; michael@0: } michael@0: ctx.restore(); michael@0: } michael@0: }, michael@0: showSpacedText: function CanvasGraphics_showSpacedText(arr) { michael@0: var current = this.current; michael@0: var font = current.font; michael@0: var fontSize = current.fontSize; michael@0: // TJ array's number is independent from fontMatrix michael@0: var textHScale = current.textHScale * 0.001 * current.fontDirection; michael@0: var arrLength = arr.length; michael@0: var vertical = font.vertical; michael@0: michael@0: for (var i = 0; i < arrLength; ++i) { michael@0: var e = arr[i]; michael@0: if (isNum(e)) { michael@0: var spacingLength = -e * fontSize * textHScale; michael@0: if (vertical) { michael@0: current.y += spacingLength; michael@0: } else { michael@0: current.x += spacingLength; michael@0: } michael@0: michael@0: } else { michael@0: this.showText(e); michael@0: } michael@0: } michael@0: }, michael@0: nextLineShowText: function CanvasGraphics_nextLineShowText(text) { michael@0: this.nextLine(); michael@0: this.showText(text); michael@0: }, michael@0: nextLineSetSpacingShowText: michael@0: function CanvasGraphics_nextLineSetSpacingShowText(wordSpacing, michael@0: charSpacing, michael@0: text) { michael@0: this.setWordSpacing(wordSpacing); michael@0: this.setCharSpacing(charSpacing); michael@0: this.nextLineShowText(text); michael@0: }, michael@0: michael@0: // Type3 fonts michael@0: setCharWidth: function CanvasGraphics_setCharWidth(xWidth, yWidth) { michael@0: // We can safely ignore this since the width should be the same michael@0: // as the width in the Widths array. michael@0: }, michael@0: setCharWidthAndBounds: function CanvasGraphics_setCharWidthAndBounds(xWidth, michael@0: yWidth, michael@0: llx, michael@0: lly, michael@0: urx, michael@0: ury) { michael@0: // TODO According to the spec we're also suppose to ignore any operators michael@0: // that set color or include images while processing this type3 font. michael@0: this.rectangle(llx, lly, urx - llx, ury - lly); michael@0: this.clip(); michael@0: this.endPath(); michael@0: }, michael@0: michael@0: // Color michael@0: setStrokeColorSpace: function CanvasGraphics_setStrokeColorSpace(raw) { michael@0: this.current.strokeColorSpace = ColorSpace.fromIR(raw); michael@0: }, michael@0: setFillColorSpace: function CanvasGraphics_setFillColorSpace(raw) { michael@0: this.current.fillColorSpace = ColorSpace.fromIR(raw); michael@0: }, michael@0: setStrokeColor: function CanvasGraphics_setStrokeColor(/*...*/) { michael@0: var cs = this.current.strokeColorSpace; michael@0: var rgbColor = cs.getRgb(arguments, 0); michael@0: var color = Util.makeCssRgb(rgbColor); michael@0: this.ctx.strokeStyle = color; michael@0: this.current.strokeColor = color; michael@0: }, michael@0: getColorN_Pattern: function CanvasGraphics_getColorN_Pattern(IR, cs) { michael@0: var pattern; michael@0: if (IR[0] == 'TilingPattern') { michael@0: var args = IR[1]; michael@0: var base = cs.base; michael@0: var color; michael@0: if (base) { michael@0: color = base.getRgb(args, 0); michael@0: } michael@0: pattern = new TilingPattern(IR, color, this.ctx, this.objs, michael@0: this.commonObjs, this.baseTransform); michael@0: } else { michael@0: pattern = getShadingPatternFromIR(IR); michael@0: } michael@0: return pattern; michael@0: }, michael@0: setStrokeColorN: function CanvasGraphics_setStrokeColorN(/*...*/) { michael@0: var cs = this.current.strokeColorSpace; michael@0: michael@0: if (cs.name == 'Pattern') { michael@0: this.current.strokeColor = this.getColorN_Pattern(arguments, cs); michael@0: } else { michael@0: this.setStrokeColor.apply(this, arguments); michael@0: } michael@0: }, michael@0: setFillColor: function CanvasGraphics_setFillColor(/*...*/) { michael@0: var cs = this.current.fillColorSpace; michael@0: var rgbColor = cs.getRgb(arguments, 0); michael@0: var color = Util.makeCssRgb(rgbColor); michael@0: this.ctx.fillStyle = color; michael@0: this.current.fillColor = color; michael@0: }, michael@0: setFillColorN: function CanvasGraphics_setFillColorN(/*...*/) { michael@0: var cs = this.current.fillColorSpace; michael@0: michael@0: if (cs.name == 'Pattern') { michael@0: this.current.fillColor = this.getColorN_Pattern(arguments, cs); michael@0: } else { michael@0: this.setFillColor.apply(this, arguments); michael@0: } michael@0: }, michael@0: setStrokeGray: function CanvasGraphics_setStrokeGray(gray) { michael@0: this.current.strokeColorSpace = ColorSpace.singletons.gray; michael@0: michael@0: var rgbColor = this.current.strokeColorSpace.getRgb(arguments, 0); michael@0: var color = Util.makeCssRgb(rgbColor); michael@0: this.ctx.strokeStyle = color; michael@0: this.current.strokeColor = color; michael@0: }, michael@0: setFillGray: function CanvasGraphics_setFillGray(gray) { michael@0: this.current.fillColorSpace = ColorSpace.singletons.gray; michael@0: michael@0: var rgbColor = this.current.fillColorSpace.getRgb(arguments, 0); michael@0: var color = Util.makeCssRgb(rgbColor); michael@0: this.ctx.fillStyle = color; michael@0: this.current.fillColor = color; michael@0: }, michael@0: setStrokeRGBColor: function CanvasGraphics_setStrokeRGBColor(r, g, b) { michael@0: this.current.strokeColorSpace = ColorSpace.singletons.rgb; michael@0: michael@0: var rgbColor = this.current.strokeColorSpace.getRgb(arguments, 0); michael@0: var color = Util.makeCssRgb(rgbColor); michael@0: this.ctx.strokeStyle = color; michael@0: this.current.strokeColor = color; michael@0: }, michael@0: setFillRGBColor: function CanvasGraphics_setFillRGBColor(r, g, b) { michael@0: this.current.fillColorSpace = ColorSpace.singletons.rgb; michael@0: michael@0: var rgbColor = this.current.fillColorSpace.getRgb(arguments, 0); michael@0: var color = Util.makeCssRgb(rgbColor); michael@0: this.ctx.fillStyle = color; michael@0: this.current.fillColor = color; michael@0: }, michael@0: setStrokeCMYKColor: function CanvasGraphics_setStrokeCMYKColor(c, m, y, k) { michael@0: this.current.strokeColorSpace = ColorSpace.singletons.cmyk; michael@0: michael@0: var color = Util.makeCssCmyk(arguments); michael@0: this.ctx.strokeStyle = color; michael@0: this.current.strokeColor = color; michael@0: }, michael@0: setFillCMYKColor: function CanvasGraphics_setFillCMYKColor(c, m, y, k) { michael@0: this.current.fillColorSpace = ColorSpace.singletons.cmyk; michael@0: michael@0: var color = Util.makeCssCmyk(arguments); michael@0: this.ctx.fillStyle = color; michael@0: this.current.fillColor = color; michael@0: }, michael@0: michael@0: shadingFill: function CanvasGraphics_shadingFill(patternIR) { michael@0: var ctx = this.ctx; michael@0: michael@0: this.save(); michael@0: var pattern = getShadingPatternFromIR(patternIR); michael@0: ctx.fillStyle = pattern.getPattern(ctx, this, true); michael@0: michael@0: var inv = ctx.mozCurrentTransformInverse; michael@0: if (inv) { michael@0: var canvas = ctx.canvas; michael@0: var width = canvas.width; michael@0: var height = canvas.height; michael@0: michael@0: var bl = Util.applyTransform([0, 0], inv); michael@0: var br = Util.applyTransform([0, height], inv); michael@0: var ul = Util.applyTransform([width, 0], inv); michael@0: var ur = Util.applyTransform([width, height], inv); michael@0: michael@0: var x0 = Math.min(bl[0], br[0], ul[0], ur[0]); michael@0: var y0 = Math.min(bl[1], br[1], ul[1], ur[1]); michael@0: var x1 = Math.max(bl[0], br[0], ul[0], ur[0]); michael@0: var y1 = Math.max(bl[1], br[1], ul[1], ur[1]); michael@0: michael@0: this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0); michael@0: } else { michael@0: // HACK to draw the gradient onto an infinite rectangle. michael@0: // PDF gradients are drawn across the entire image while michael@0: // Canvas only allows gradients to be drawn in a rectangle michael@0: // The following bug should allow us to remove this. michael@0: // https://bugzilla.mozilla.org/show_bug.cgi?id=664884 michael@0: michael@0: this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10); michael@0: } michael@0: michael@0: this.restore(); michael@0: }, michael@0: michael@0: // Images michael@0: beginInlineImage: function CanvasGraphics_beginInlineImage() { michael@0: error('Should not call beginInlineImage'); michael@0: }, michael@0: beginImageData: function CanvasGraphics_beginImageData() { michael@0: error('Should not call beginImageData'); michael@0: }, michael@0: michael@0: paintFormXObjectBegin: function CanvasGraphics_paintFormXObjectBegin(matrix, michael@0: bbox) { michael@0: this.save(); michael@0: this.baseTransformStack.push(this.baseTransform); michael@0: michael@0: if (matrix && isArray(matrix) && 6 == matrix.length) { michael@0: this.transform.apply(this, matrix); michael@0: } michael@0: michael@0: this.baseTransform = this.ctx.mozCurrentTransform; michael@0: michael@0: if (bbox && isArray(bbox) && 4 == bbox.length) { michael@0: var width = bbox[2] - bbox[0]; michael@0: var height = bbox[3] - bbox[1]; michael@0: this.rectangle(bbox[0], bbox[1], width, height); michael@0: this.clip(); michael@0: this.endPath(); michael@0: } michael@0: }, michael@0: michael@0: paintFormXObjectEnd: function CanvasGraphics_paintFormXObjectEnd() { michael@0: this.restore(); michael@0: this.baseTransform = this.baseTransformStack.pop(); michael@0: }, michael@0: michael@0: beginGroup: function CanvasGraphics_beginGroup(group) { michael@0: this.save(); michael@0: var currentCtx = this.ctx; michael@0: // TODO non-isolated groups - according to Rik at adobe non-isolated michael@0: // group results aren't usually that different and they even have tools michael@0: // that ignore this setting. Notes from Rik on implmenting: michael@0: // - When you encounter an transparency group, create a new canvas with michael@0: // the dimensions of the bbox michael@0: // - copy the content from the previous canvas to the new canvas michael@0: // - draw as usual michael@0: // - remove the backdrop alpha: michael@0: // alphaNew = 1 - (1 - alpha)/(1 - alphaBackdrop) with 'alpha' the alpha michael@0: // value of your transparency group and 'alphaBackdrop' the alpha of the michael@0: // backdrop michael@0: // - remove background color: michael@0: // colorNew = color - alphaNew *colorBackdrop /(1 - alphaNew) michael@0: if (!group.isolated) { michael@0: info('TODO: Support non-isolated groups.'); michael@0: } michael@0: michael@0: // TODO knockout - supposedly possible with the clever use of compositing michael@0: // modes. michael@0: if (group.knockout) { michael@0: warn('Knockout groups not supported.'); michael@0: } michael@0: michael@0: var currentTransform = currentCtx.mozCurrentTransform; michael@0: if (group.matrix) { michael@0: currentCtx.transform.apply(currentCtx, group.matrix); michael@0: } michael@0: assert(group.bbox, 'Bounding box is required.'); michael@0: michael@0: // Based on the current transform figure out how big the bounding box michael@0: // will actually be. michael@0: var bounds = Util.getAxialAlignedBoundingBox( michael@0: group.bbox, michael@0: currentCtx.mozCurrentTransform); michael@0: // Clip the bounding box to the current canvas. michael@0: var canvasBounds = [0, michael@0: 0, michael@0: currentCtx.canvas.width, michael@0: currentCtx.canvas.height]; michael@0: bounds = Util.intersect(bounds, canvasBounds) || [0, 0, 0, 0]; michael@0: // Use ceil in case we're between sizes so we don't create canvas that is michael@0: // too small and make the canvas at least 1x1 pixels. michael@0: var offsetX = Math.floor(bounds[0]); michael@0: var offsetY = Math.floor(bounds[1]); michael@0: var drawnWidth = Math.max(Math.ceil(bounds[2]) - offsetX, 1); michael@0: var drawnHeight = Math.max(Math.ceil(bounds[3]) - offsetY, 1); michael@0: var scaleX = 1, scaleY = 1; michael@0: if (drawnWidth > MAX_GROUP_SIZE) { michael@0: scaleX = drawnWidth / MAX_GROUP_SIZE; michael@0: drawnWidth = MAX_GROUP_SIZE; michael@0: } michael@0: if (drawnHeight > MAX_GROUP_SIZE) { michael@0: scaleY = drawnHeight / MAX_GROUP_SIZE; michael@0: drawnHeight = MAX_GROUP_SIZE; michael@0: } michael@0: michael@0: var cacheId = 'groupAt' + this.groupLevel; michael@0: if (group.smask) { michael@0: // Using two cache entries is case if masks are used one after another. michael@0: cacheId += '_smask_' + ((this.smaskCounter++) % 2); michael@0: } michael@0: var scratchCanvas = CachedCanvases.getCanvas( michael@0: cacheId, drawnWidth, drawnHeight, true); michael@0: var groupCtx = scratchCanvas.context; michael@0: michael@0: // Since we created a new canvas that is just the size of the bounding box michael@0: // we have to translate the group ctx. michael@0: groupCtx.scale(1 / scaleX, 1 / scaleY); michael@0: groupCtx.translate(-offsetX, -offsetY); michael@0: groupCtx.transform.apply(groupCtx, currentTransform); michael@0: michael@0: if (group.smask) { michael@0: // Saving state and cached mask to be used in setGState. michael@0: this.smaskStack.push({ michael@0: canvas: scratchCanvas.canvas, michael@0: context: groupCtx, michael@0: offsetX: offsetX, michael@0: offsetY: offsetY, michael@0: scaleX: scaleX, michael@0: scaleY: scaleY, michael@0: subtype: group.smask.subtype, michael@0: backdrop: group.smask.backdrop, michael@0: colorSpace: group.colorSpace && ColorSpace.fromIR(group.colorSpace) michael@0: }); michael@0: } else { michael@0: // Setup the current ctx so when the group is popped we draw it at the michael@0: // right location. michael@0: currentCtx.setTransform(1, 0, 0, 1, 0, 0); michael@0: currentCtx.translate(offsetX, offsetY); michael@0: currentCtx.scale(scaleX, scaleY); michael@0: } michael@0: // The transparency group inherits all off the current graphics state michael@0: // except the blend mode, soft mask, and alpha constants. michael@0: copyCtxState(currentCtx, groupCtx); michael@0: this.ctx = groupCtx; michael@0: this.setGState([ michael@0: ['BM', 'Normal'], michael@0: ['ca', 1], michael@0: ['CA', 1] michael@0: ]); michael@0: this.groupStack.push(currentCtx); michael@0: this.groupLevel++; michael@0: }, michael@0: michael@0: endGroup: function CanvasGraphics_endGroup(group) { michael@0: this.groupLevel--; michael@0: var groupCtx = this.ctx; michael@0: this.ctx = this.groupStack.pop(); michael@0: // Turn off image smoothing to avoid sub pixel interpolation which can michael@0: // look kind of blurry for some pdfs. michael@0: if ('imageSmoothingEnabled' in this.ctx) { michael@0: this.ctx.imageSmoothingEnabled = false; michael@0: } else { michael@0: this.ctx.mozImageSmoothingEnabled = false; michael@0: } michael@0: if (group.smask) { michael@0: this.tempSMask = this.smaskStack.pop(); michael@0: } else { michael@0: this.ctx.drawImage(groupCtx.canvas, 0, 0); michael@0: } michael@0: this.restore(); michael@0: }, michael@0: michael@0: beginAnnotations: function CanvasGraphics_beginAnnotations() { michael@0: this.save(); michael@0: this.current = new CanvasExtraState(); michael@0: }, michael@0: michael@0: endAnnotations: function CanvasGraphics_endAnnotations() { michael@0: this.restore(); michael@0: }, michael@0: michael@0: beginAnnotation: function CanvasGraphics_beginAnnotation(rect, transform, michael@0: matrix) { michael@0: this.save(); michael@0: michael@0: if (rect && isArray(rect) && 4 == rect.length) { michael@0: var width = rect[2] - rect[0]; michael@0: var height = rect[3] - rect[1]; michael@0: this.rectangle(rect[0], rect[1], width, height); michael@0: this.clip(); michael@0: this.endPath(); michael@0: } michael@0: michael@0: this.transform.apply(this, transform); michael@0: this.transform.apply(this, matrix); michael@0: }, michael@0: michael@0: endAnnotation: function CanvasGraphics_endAnnotation() { michael@0: this.restore(); michael@0: }, michael@0: michael@0: paintJpegXObject: function CanvasGraphics_paintJpegXObject(objId, w, h) { michael@0: var domImage = this.objs.get(objId); michael@0: if (!domImage) { michael@0: warn('Dependent image isn\'t ready yet'); michael@0: return; michael@0: } michael@0: michael@0: this.save(); michael@0: michael@0: var ctx = this.ctx; michael@0: // scale the image to the unit square michael@0: ctx.scale(1 / w, -1 / h); michael@0: michael@0: ctx.drawImage(domImage, 0, 0, domImage.width, domImage.height, michael@0: 0, -h, w, h); michael@0: if (this.imageLayer) { michael@0: var currentTransform = ctx.mozCurrentTransformInverse; michael@0: var position = this.getCanvasPosition(0, 0); michael@0: this.imageLayer.appendImage({ michael@0: objId: objId, michael@0: left: position[0], michael@0: top: position[1], michael@0: width: w / currentTransform[0], michael@0: height: h / currentTransform[3] michael@0: }); michael@0: } michael@0: this.restore(); michael@0: }, michael@0: michael@0: paintImageMaskXObject: function CanvasGraphics_paintImageMaskXObject(img) { michael@0: var ctx = this.ctx; michael@0: var width = img.width, height = img.height; michael@0: michael@0: var glyph = this.processingType3; michael@0: michael@0: if (COMPILE_TYPE3_GLYPHS && glyph && !('compiled' in glyph)) { michael@0: var MAX_SIZE_TO_COMPILE = 1000; michael@0: if (width <= MAX_SIZE_TO_COMPILE && height <= MAX_SIZE_TO_COMPILE) { michael@0: glyph.compiled = michael@0: compileType3Glyph({data: img.data, width: width, height: height}); michael@0: } else { michael@0: glyph.compiled = null; michael@0: } michael@0: } michael@0: michael@0: if (glyph && glyph.compiled) { michael@0: glyph.compiled(ctx); michael@0: return; michael@0: } michael@0: michael@0: var maskCanvas = CachedCanvases.getCanvas('maskCanvas', width, height); michael@0: var maskCtx = maskCanvas.context; michael@0: maskCtx.save(); michael@0: michael@0: putBinaryImageMask(maskCtx, img); michael@0: michael@0: maskCtx.globalCompositeOperation = 'source-in'; michael@0: michael@0: var fillColor = this.current.fillColor; michael@0: maskCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') && michael@0: fillColor.type === 'Pattern') ? michael@0: fillColor.getPattern(maskCtx, this) : fillColor; michael@0: maskCtx.fillRect(0, 0, width, height); michael@0: michael@0: maskCtx.restore(); michael@0: michael@0: this.paintInlineImageXObject(maskCanvas.canvas); michael@0: }, michael@0: michael@0: paintImageMaskXObjectRepeat: michael@0: function CanvasGraphics_paintImageMaskXObjectRepeat(imgData, scaleX, michael@0: scaleY, positions) { michael@0: var width = imgData.width; michael@0: var height = imgData.height; michael@0: var ctx = this.ctx; michael@0: michael@0: var maskCanvas = CachedCanvases.getCanvas('maskCanvas', width, height); michael@0: var maskCtx = maskCanvas.context; michael@0: maskCtx.save(); michael@0: michael@0: putBinaryImageMask(maskCtx, imgData); michael@0: michael@0: maskCtx.globalCompositeOperation = 'source-in'; michael@0: michael@0: var fillColor = this.current.fillColor; michael@0: maskCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') && michael@0: fillColor.type === 'Pattern') ? michael@0: fillColor.getPattern(maskCtx, this) : fillColor; michael@0: maskCtx.fillRect(0, 0, width, height); michael@0: michael@0: maskCtx.restore(); michael@0: michael@0: for (var i = 0, ii = positions.length; i < ii; i += 2) { michael@0: ctx.save(); michael@0: ctx.transform(scaleX, 0, 0, scaleY, positions[i], positions[i + 1]); michael@0: ctx.scale(1, -1); michael@0: ctx.drawImage(maskCanvas.canvas, 0, 0, width, height, michael@0: 0, -1, 1, 1); michael@0: ctx.restore(); michael@0: } michael@0: }, michael@0: michael@0: paintImageMaskXObjectGroup: michael@0: function CanvasGraphics_paintImageMaskXObjectGroup(images) { michael@0: var ctx = this.ctx; michael@0: michael@0: for (var i = 0, ii = images.length; i < ii; i++) { michael@0: var image = images[i]; michael@0: var width = image.width, height = image.height; michael@0: michael@0: var maskCanvas = CachedCanvases.getCanvas('maskCanvas', width, height); michael@0: var maskCtx = maskCanvas.context; michael@0: maskCtx.save(); michael@0: michael@0: putBinaryImageMask(maskCtx, image); michael@0: michael@0: maskCtx.globalCompositeOperation = 'source-in'; michael@0: michael@0: var fillColor = this.current.fillColor; michael@0: maskCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') && michael@0: fillColor.type === 'Pattern') ? michael@0: fillColor.getPattern(maskCtx, this) : fillColor; michael@0: maskCtx.fillRect(0, 0, width, height); michael@0: michael@0: maskCtx.restore(); michael@0: michael@0: ctx.save(); michael@0: ctx.transform.apply(ctx, image.transform); michael@0: ctx.scale(1, -1); michael@0: ctx.drawImage(maskCanvas.canvas, 0, 0, width, height, michael@0: 0, -1, 1, 1); michael@0: ctx.restore(); michael@0: } michael@0: }, michael@0: michael@0: paintImageXObject: function CanvasGraphics_paintImageXObject(objId) { michael@0: var imgData = this.objs.get(objId); michael@0: if (!imgData) { michael@0: warn('Dependent image isn\'t ready yet'); michael@0: return; michael@0: } michael@0: michael@0: this.paintInlineImageXObject(imgData); michael@0: }, michael@0: michael@0: paintImageXObjectRepeat: michael@0: function CanvasGraphics_paintImageXObjectRepeat(objId, scaleX, scaleY, michael@0: positions) { michael@0: var imgData = this.objs.get(objId); michael@0: if (!imgData) { michael@0: warn('Dependent image isn\'t ready yet'); michael@0: return; michael@0: } michael@0: michael@0: var width = imgData.width; michael@0: var height = imgData.height; michael@0: var map = []; michael@0: for (var i = 0, ii = positions.length; i < ii; i += 2) { michael@0: map.push({transform: [scaleX, 0, 0, scaleY, positions[i], michael@0: positions[i + 1]], x: 0, y: 0, w: width, h: height}); michael@0: } michael@0: this.paintInlineImageXObjectGroup(imgData, map); michael@0: }, michael@0: michael@0: paintInlineImageXObject: michael@0: function CanvasGraphics_paintInlineImageXObject(imgData) { michael@0: var width = imgData.width; michael@0: var height = imgData.height; michael@0: var ctx = this.ctx; michael@0: michael@0: this.save(); michael@0: // scale the image to the unit square michael@0: ctx.scale(1 / width, -1 / height); michael@0: michael@0: var currentTransform = ctx.mozCurrentTransformInverse; michael@0: var a = currentTransform[0], b = currentTransform[1]; michael@0: var widthScale = Math.max(Math.sqrt(a * a + b * b), 1); michael@0: var c = currentTransform[2], d = currentTransform[3]; michael@0: var heightScale = Math.max(Math.sqrt(c * c + d * d), 1); michael@0: michael@0: var imgToPaint, tmpCanvas; michael@0: // instanceof HTMLElement does not work in jsdom node.js module michael@0: if (imgData instanceof HTMLElement || !imgData.data) { michael@0: imgToPaint = imgData; michael@0: } else { michael@0: tmpCanvas = CachedCanvases.getCanvas('inlineImage', width, height); michael@0: var tmpCtx = tmpCanvas.context; michael@0: putBinaryImageData(tmpCtx, imgData); michael@0: imgToPaint = tmpCanvas.canvas; michael@0: } michael@0: michael@0: var paintWidth = width, paintHeight = height; michael@0: var tmpCanvasId = 'prescale1'; michael@0: // Vertial or horizontal scaling shall not be more than 2 to not loose the michael@0: // pixels during drawImage operation, painting on the temporary canvas(es) michael@0: // that are twice smaller in size michael@0: while ((widthScale > 2 && paintWidth > 1) || michael@0: (heightScale > 2 && paintHeight > 1)) { michael@0: var newWidth = paintWidth, newHeight = paintHeight; michael@0: if (widthScale > 2 && paintWidth > 1) { michael@0: newWidth = Math.ceil(paintWidth / 2); michael@0: widthScale /= paintWidth / newWidth; michael@0: } michael@0: if (heightScale > 2 && paintHeight > 1) { michael@0: newHeight = Math.ceil(paintHeight / 2); michael@0: heightScale /= paintHeight / newHeight; michael@0: } michael@0: tmpCanvas = CachedCanvases.getCanvas(tmpCanvasId, newWidth, newHeight); michael@0: tmpCtx = tmpCanvas.context; michael@0: tmpCtx.clearRect(0, 0, newWidth, newHeight); michael@0: tmpCtx.drawImage(imgToPaint, 0, 0, paintWidth, paintHeight, michael@0: 0, 0, newWidth, newHeight); michael@0: imgToPaint = tmpCanvas.canvas; michael@0: paintWidth = newWidth; michael@0: paintHeight = newHeight; michael@0: tmpCanvasId = tmpCanvasId === 'prescale1' ? 'prescale2' : 'prescale1'; michael@0: } michael@0: ctx.drawImage(imgToPaint, 0, 0, paintWidth, paintHeight, michael@0: 0, -height, width, height); michael@0: michael@0: if (this.imageLayer) { michael@0: var position = this.getCanvasPosition(0, -height); michael@0: this.imageLayer.appendImage({ michael@0: imgData: imgData, michael@0: left: position[0], michael@0: top: position[1], michael@0: width: width / currentTransform[0], michael@0: height: height / currentTransform[3] michael@0: }); michael@0: } michael@0: this.restore(); michael@0: }, michael@0: michael@0: paintInlineImageXObjectGroup: michael@0: function CanvasGraphics_paintInlineImageXObjectGroup(imgData, map) { michael@0: var ctx = this.ctx; michael@0: var w = imgData.width; michael@0: var h = imgData.height; michael@0: michael@0: var tmpCanvas = CachedCanvases.getCanvas('inlineImage', w, h); michael@0: var tmpCtx = tmpCanvas.context; michael@0: putBinaryImageData(tmpCtx, imgData); michael@0: michael@0: for (var i = 0, ii = map.length; i < ii; i++) { michael@0: var entry = map[i]; michael@0: ctx.save(); michael@0: ctx.transform.apply(ctx, entry.transform); michael@0: ctx.scale(1, -1); michael@0: ctx.drawImage(tmpCanvas.canvas, entry.x, entry.y, entry.w, entry.h, michael@0: 0, -1, 1, 1); michael@0: if (this.imageLayer) { michael@0: var position = this.getCanvasPosition(entry.x, entry.y); michael@0: this.imageLayer.appendImage({ michael@0: imgData: imgData, michael@0: left: position[0], michael@0: top: position[1], michael@0: width: w, michael@0: height: h michael@0: }); michael@0: } michael@0: ctx.restore(); michael@0: } michael@0: }, michael@0: michael@0: paintSolidColorImageMask: michael@0: function CanvasGraphics_paintSolidColorImageMask() { michael@0: this.ctx.fillRect(0, 0, 1, 1); michael@0: }, michael@0: michael@0: // Marked content michael@0: michael@0: markPoint: function CanvasGraphics_markPoint(tag) { michael@0: // TODO Marked content. michael@0: }, michael@0: markPointProps: function CanvasGraphics_markPointProps(tag, properties) { michael@0: // TODO Marked content. michael@0: }, michael@0: beginMarkedContent: function CanvasGraphics_beginMarkedContent(tag) { michael@0: // TODO Marked content. michael@0: }, michael@0: beginMarkedContentProps: function CanvasGraphics_beginMarkedContentProps( michael@0: tag, properties) { michael@0: // TODO Marked content. michael@0: }, michael@0: endMarkedContent: function CanvasGraphics_endMarkedContent() { michael@0: // TODO Marked content. michael@0: }, michael@0: michael@0: // Compatibility michael@0: michael@0: beginCompat: function CanvasGraphics_beginCompat() { michael@0: // TODO ignore undefined operators (should we do that anyway?) michael@0: }, michael@0: endCompat: function CanvasGraphics_endCompat() { michael@0: // TODO stop ignoring undefined operators michael@0: }, michael@0: michael@0: // Helper functions michael@0: michael@0: consumePath: function CanvasGraphics_consumePath() { michael@0: if (this.pendingClip) { michael@0: if (this.pendingClip == EO_CLIP) { michael@0: if ('mozFillRule' in this.ctx) { michael@0: this.ctx.mozFillRule = 'evenodd'; michael@0: this.ctx.clip(); michael@0: this.ctx.mozFillRule = 'nonzero'; michael@0: } else { michael@0: try { michael@0: this.ctx.clip('evenodd'); michael@0: } catch (ex) { michael@0: // shouldn't really happen, but browsers might think differently michael@0: this.ctx.clip(); michael@0: } michael@0: } michael@0: } else { michael@0: this.ctx.clip(); michael@0: } michael@0: this.pendingClip = null; michael@0: } michael@0: this.ctx.beginPath(); michael@0: }, michael@0: getSinglePixelWidth: function CanvasGraphics_getSinglePixelWidth(scale) { michael@0: var inverse = this.ctx.mozCurrentTransformInverse; michael@0: // max of the current horizontal and vertical scale michael@0: return Math.sqrt(Math.max( michael@0: (inverse[0] * inverse[0] + inverse[1] * inverse[1]), michael@0: (inverse[2] * inverse[2] + inverse[3] * inverse[3]))); michael@0: }, michael@0: getCanvasPosition: function CanvasGraphics_getCanvasPosition(x, y) { michael@0: var transform = this.ctx.mozCurrentTransform; michael@0: return [ michael@0: transform[0] * x + transform[2] * y + transform[4], michael@0: transform[1] * x + transform[3] * y + transform[5] michael@0: ]; michael@0: } michael@0: }; michael@0: michael@0: for (var op in OPS) { michael@0: CanvasGraphics.prototype[OPS[op]] = CanvasGraphics.prototype[op]; michael@0: } michael@0: michael@0: return CanvasGraphics; michael@0: })(); michael@0: michael@0: michael@0: michael@0: var WebGLUtils = (function WebGLUtilsClosure() { michael@0: function loadShader(gl, code, shaderType) { michael@0: var shader = gl.createShader(shaderType); michael@0: gl.shaderSource(shader, code); michael@0: gl.compileShader(shader); michael@0: var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS); michael@0: if (!compiled) { michael@0: var errorMsg = gl.getShaderInfoLog(shader); michael@0: throw new Error('Error during shader compilation: ' + errorMsg); michael@0: } michael@0: return shader; michael@0: } michael@0: function createVertexShader(gl, code) { michael@0: return loadShader(gl, code, gl.VERTEX_SHADER); michael@0: } michael@0: function createFragmentShader(gl, code) { michael@0: return loadShader(gl, code, gl.FRAGMENT_SHADER); michael@0: } michael@0: function createProgram(gl, shaders) { michael@0: var program = gl.createProgram(); michael@0: for (var i = 0, ii = shaders.length; i < ii; ++i) { michael@0: gl.attachShader(program, shaders[i]); michael@0: } michael@0: gl.linkProgram(program); michael@0: var linked = gl.getProgramParameter(program, gl.LINK_STATUS); michael@0: if (!linked) { michael@0: var errorMsg = gl.getProgramInfoLog(program); michael@0: throw new Error('Error during program linking: ' + errorMsg); michael@0: } michael@0: return program; michael@0: } michael@0: function createTexture(gl, image, textureId) { michael@0: gl.activeTexture(textureId); michael@0: var texture = gl.createTexture(); michael@0: gl.bindTexture(gl.TEXTURE_2D, texture); michael@0: michael@0: // Set the parameters so we can render any size image. michael@0: gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); michael@0: gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); michael@0: gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); michael@0: gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); michael@0: michael@0: // Upload the image into the texture. michael@0: gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); michael@0: return texture; michael@0: } michael@0: michael@0: var currentGL, currentCanvas; michael@0: function generageGL() { michael@0: if (currentGL) { michael@0: return; michael@0: } michael@0: currentCanvas = document.createElement('canvas'); michael@0: currentGL = currentCanvas.getContext('webgl', michael@0: { premultipliedalpha: false }); michael@0: } michael@0: michael@0: var smaskVertexShaderCode = '\ michael@0: attribute vec2 a_position; \ michael@0: attribute vec2 a_texCoord; \ michael@0: \ michael@0: uniform vec2 u_resolution; \ michael@0: \ michael@0: varying vec2 v_texCoord; \ michael@0: \ michael@0: void main() { \ michael@0: vec2 clipSpace = (a_position / u_resolution) * 2.0 - 1.0; \ michael@0: gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1); \ michael@0: \ michael@0: v_texCoord = a_texCoord; \ michael@0: } '; michael@0: michael@0: var smaskFragmentShaderCode = '\ michael@0: precision mediump float; \ michael@0: \ michael@0: uniform vec4 u_backdrop; \ michael@0: uniform int u_subtype; \ michael@0: uniform sampler2D u_image; \ michael@0: uniform sampler2D u_mask; \ michael@0: \ michael@0: varying vec2 v_texCoord; \ michael@0: \ michael@0: void main() { \ michael@0: vec4 imageColor = texture2D(u_image, v_texCoord); \ michael@0: vec4 maskColor = texture2D(u_mask, v_texCoord); \ michael@0: if (u_backdrop.a > 0.0) { \ michael@0: maskColor.rgb = maskColor.rgb * maskColor.a + \ michael@0: u_backdrop.rgb * (1.0 - maskColor.a); \ michael@0: } \ michael@0: float lum; \ michael@0: if (u_subtype == 0) { \ michael@0: lum = maskColor.a; \ michael@0: } else { \ michael@0: lum = maskColor.r * 0.3 + maskColor.g * 0.59 + \ michael@0: maskColor.b * 0.11; \ michael@0: } \ michael@0: imageColor.a *= lum; \ michael@0: imageColor.rgb *= imageColor.a; \ michael@0: gl_FragColor = imageColor; \ michael@0: } '; michael@0: michael@0: var smaskCache = null; michael@0: michael@0: function initSmaskGL() { michael@0: var canvas, gl; michael@0: michael@0: generageGL(); michael@0: canvas = currentCanvas; michael@0: currentCanvas = null; michael@0: gl = currentGL; michael@0: currentGL = null; michael@0: michael@0: // setup a GLSL program michael@0: var vertexShader = createVertexShader(gl, smaskVertexShaderCode); michael@0: var fragmentShader = createFragmentShader(gl, smaskFragmentShaderCode); michael@0: var program = createProgram(gl, [vertexShader, fragmentShader]); michael@0: gl.useProgram(program); michael@0: michael@0: var cache = {}; michael@0: cache.gl = gl; michael@0: cache.canvas = canvas; michael@0: cache.resolutionLocation = gl.getUniformLocation(program, 'u_resolution'); michael@0: cache.positionLocation = gl.getAttribLocation(program, 'a_position'); michael@0: cache.backdropLocation = gl.getUniformLocation(program, 'u_backdrop'); michael@0: cache.subtypeLocation = gl.getUniformLocation(program, 'u_subtype'); michael@0: michael@0: var texCoordLocation = gl.getAttribLocation(program, 'a_texCoord'); michael@0: var texLayerLocation = gl.getUniformLocation(program, 'u_image'); michael@0: var texMaskLocation = gl.getUniformLocation(program, 'u_mask'); michael@0: michael@0: // provide texture coordinates for the rectangle. michael@0: var texCoordBuffer = gl.createBuffer(); michael@0: gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer); michael@0: gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ michael@0: 0.0, 0.0, michael@0: 1.0, 0.0, michael@0: 0.0, 1.0, michael@0: 0.0, 1.0, michael@0: 1.0, 0.0, michael@0: 1.0, 1.0]), gl.STATIC_DRAW); michael@0: gl.enableVertexAttribArray(texCoordLocation); michael@0: gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0); michael@0: michael@0: gl.uniform1i(texLayerLocation, 0); michael@0: gl.uniform1i(texMaskLocation, 1); michael@0: michael@0: smaskCache = cache; michael@0: } michael@0: michael@0: function composeSMask(layer, mask, properties) { michael@0: var width = layer.width, height = layer.height; michael@0: michael@0: if (!smaskCache) { michael@0: initSmaskGL(); michael@0: } michael@0: var cache = smaskCache,canvas = cache.canvas, gl = cache.gl; michael@0: canvas.width = width; michael@0: canvas.height = height; michael@0: gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); michael@0: gl.uniform2f(cache.resolutionLocation, width, height); michael@0: michael@0: if (properties.backdrop) { michael@0: gl.uniform4f(cache.resolutionLocation, properties.backdrop[0], michael@0: properties.backdrop[1], properties.backdrop[2], 1); michael@0: } else { michael@0: gl.uniform4f(cache.resolutionLocation, 0, 0, 0, 0); michael@0: } michael@0: gl.uniform1i(cache.subtypeLocation, michael@0: properties.subtype === 'Luminosity' ? 1 : 0); michael@0: michael@0: // Create a textures michael@0: var texture = createTexture(gl, layer, gl.TEXTURE0); michael@0: var maskTexture = createTexture(gl, mask, gl.TEXTURE1); michael@0: michael@0: michael@0: // Create a buffer and put a single clipspace rectangle in michael@0: // it (2 triangles) michael@0: var buffer = gl.createBuffer(); michael@0: gl.bindBuffer(gl.ARRAY_BUFFER, buffer); michael@0: gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ michael@0: 0, 0, michael@0: width, 0, michael@0: 0, height, michael@0: 0, height, michael@0: width, 0, michael@0: width, height]), gl.STATIC_DRAW); michael@0: gl.enableVertexAttribArray(cache.positionLocation); michael@0: gl.vertexAttribPointer(cache.positionLocation, 2, gl.FLOAT, false, 0, 0); michael@0: michael@0: // draw michael@0: gl.clearColor(0, 0, 0, 0); michael@0: gl.enable(gl.BLEND); michael@0: gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); michael@0: gl.clear(gl.COLOR_BUFFER_BIT); michael@0: michael@0: gl.drawArrays(gl.TRIANGLES, 0, 6); michael@0: michael@0: gl.flush(); michael@0: michael@0: gl.deleteTexture(texture); michael@0: gl.deleteTexture(maskTexture); michael@0: gl.deleteBuffer(buffer); michael@0: michael@0: return canvas; michael@0: } michael@0: michael@0: var figuresVertexShaderCode = '\ michael@0: attribute vec2 a_position; \ michael@0: attribute vec3 a_color; \ michael@0: \ michael@0: uniform vec2 u_resolution; \ michael@0: uniform vec2 u_scale; \ michael@0: uniform vec2 u_offset; \ michael@0: \ michael@0: varying vec4 v_color; \ michael@0: \ michael@0: void main() { \ michael@0: vec2 position = (a_position + u_offset) * u_scale; \ michael@0: vec2 clipSpace = (position / u_resolution) * 2.0 - 1.0; \ michael@0: gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1); \ michael@0: \ michael@0: v_color = vec4(a_color / 255.0, 1.0); \ michael@0: } '; michael@0: michael@0: var figuresFragmentShaderCode = '\ michael@0: precision mediump float; \ michael@0: \ michael@0: varying vec4 v_color; \ michael@0: \ michael@0: void main() { \ michael@0: gl_FragColor = v_color; \ michael@0: } '; michael@0: michael@0: var figuresCache = null; michael@0: michael@0: function initFiguresGL() { michael@0: var canvas, gl; michael@0: michael@0: generageGL(); michael@0: canvas = currentCanvas; michael@0: currentCanvas = null; michael@0: gl = currentGL; michael@0: currentGL = null; michael@0: michael@0: // setup a GLSL program michael@0: var vertexShader = createVertexShader(gl, figuresVertexShaderCode); michael@0: var fragmentShader = createFragmentShader(gl, figuresFragmentShaderCode); michael@0: var program = createProgram(gl, [vertexShader, fragmentShader]); michael@0: gl.useProgram(program); michael@0: michael@0: var cache = {}; michael@0: cache.gl = gl; michael@0: cache.canvas = canvas; michael@0: cache.resolutionLocation = gl.getUniformLocation(program, 'u_resolution'); michael@0: cache.scaleLocation = gl.getUniformLocation(program, 'u_scale'); michael@0: cache.offsetLocation = gl.getUniformLocation(program, 'u_offset'); michael@0: cache.positionLocation = gl.getAttribLocation(program, 'a_position'); michael@0: cache.colorLocation = gl.getAttribLocation(program, 'a_color'); michael@0: michael@0: figuresCache = cache; michael@0: } michael@0: michael@0: function drawFigures(width, height, backgroundColor, figures, context) { michael@0: if (!figuresCache) { michael@0: initFiguresGL(); michael@0: } michael@0: var cache = figuresCache, canvas = cache.canvas, gl = cache.gl; michael@0: michael@0: canvas.width = width; michael@0: canvas.height = height; michael@0: gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); michael@0: gl.uniform2f(cache.resolutionLocation, width, height); michael@0: michael@0: // count triangle points michael@0: var count = 0; michael@0: var i, ii, rows; michael@0: for (i = 0, ii = figures.length; i < ii; i++) { michael@0: switch (figures[i].type) { michael@0: case 'lattice': michael@0: rows = (figures[i].coords.length / figures[i].verticesPerRow) | 0; michael@0: count += (rows - 1) * (figures[i].verticesPerRow - 1) * 6; michael@0: break; michael@0: case 'triangles': michael@0: count += figures[i].coords.length; michael@0: break; michael@0: } michael@0: } michael@0: // transfer data michael@0: var coords = new Float32Array(count * 2); michael@0: var colors = new Uint8Array(count * 3); michael@0: var coordsMap = context.coords, colorsMap = context.colors; michael@0: var pIndex = 0, cIndex = 0; michael@0: for (i = 0, ii = figures.length; i < ii; i++) { michael@0: var figure = figures[i], ps = figure.coords, cs = figure.colors; michael@0: switch (figure.type) { michael@0: case 'lattice': michael@0: var cols = figure.verticesPerRow; michael@0: rows = (ps.length / cols) | 0; michael@0: for (var row = 1; row < rows; row++) { michael@0: var offset = row * cols + 1; michael@0: for (var col = 1; col < cols; col++, offset++) { michael@0: coords[pIndex] = coordsMap[ps[offset - cols - 1]]; michael@0: coords[pIndex + 1] = coordsMap[ps[offset - cols - 1] + 1]; michael@0: coords[pIndex + 2] = coordsMap[ps[offset - cols]]; michael@0: coords[pIndex + 3] = coordsMap[ps[offset - cols] + 1]; michael@0: coords[pIndex + 4] = coordsMap[ps[offset - 1]]; michael@0: coords[pIndex + 5] = coordsMap[ps[offset - 1] + 1]; michael@0: colors[cIndex] = colorsMap[cs[offset - cols - 1]]; michael@0: colors[cIndex + 1] = colorsMap[cs[offset - cols - 1] + 1]; michael@0: colors[cIndex + 2] = colorsMap[cs[offset - cols - 1] + 2]; michael@0: colors[cIndex + 3] = colorsMap[cs[offset - cols]]; michael@0: colors[cIndex + 4] = colorsMap[cs[offset - cols] + 1]; michael@0: colors[cIndex + 5] = colorsMap[cs[offset - cols] + 2]; michael@0: colors[cIndex + 6] = colorsMap[cs[offset - 1]]; michael@0: colors[cIndex + 7] = colorsMap[cs[offset - 1] + 1]; michael@0: colors[cIndex + 8] = colorsMap[cs[offset - 1] + 2]; michael@0: michael@0: coords[pIndex + 6] = coords[pIndex + 2]; michael@0: coords[pIndex + 7] = coords[pIndex + 3]; michael@0: coords[pIndex + 8] = coords[pIndex + 4]; michael@0: coords[pIndex + 9] = coords[pIndex + 5]; michael@0: coords[pIndex + 10] = coordsMap[ps[offset]]; michael@0: coords[pIndex + 11] = coordsMap[ps[offset] + 1]; michael@0: colors[cIndex + 9] = colors[cIndex + 3]; michael@0: colors[cIndex + 10] = colors[cIndex + 4]; michael@0: colors[cIndex + 11] = colors[cIndex + 5]; michael@0: colors[cIndex + 12] = colors[cIndex + 6]; michael@0: colors[cIndex + 13] = colors[cIndex + 7]; michael@0: colors[cIndex + 14] = colors[cIndex + 8]; michael@0: colors[cIndex + 15] = colorsMap[cs[offset]]; michael@0: colors[cIndex + 16] = colorsMap[cs[offset] + 1]; michael@0: colors[cIndex + 17] = colorsMap[cs[offset] + 2]; michael@0: pIndex += 12; michael@0: cIndex += 18; michael@0: } michael@0: } michael@0: break; michael@0: case 'triangles': michael@0: for (var j = 0, jj = ps.length; j < jj; j++) { michael@0: coords[pIndex] = coordsMap[ps[j]]; michael@0: coords[pIndex + 1] = coordsMap[ps[j] + 1]; michael@0: colors[cIndex] = colorsMap[cs[i]]; michael@0: colors[cIndex + 1] = colorsMap[cs[j] + 1]; michael@0: colors[cIndex + 2] = colorsMap[cs[j] + 2]; michael@0: pIndex += 2; michael@0: cIndex += 3; michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // draw michael@0: if (backgroundColor) { michael@0: gl.clearColor(backgroundColor[0] / 255, backgroundColor[1] / 255, michael@0: backgroundColor[2] / 255, 1.0); michael@0: } else { michael@0: gl.clearColor(0, 0, 0, 0); michael@0: } michael@0: gl.clear(gl.COLOR_BUFFER_BIT); michael@0: michael@0: var coordsBuffer = gl.createBuffer(); michael@0: gl.bindBuffer(gl.ARRAY_BUFFER, coordsBuffer); michael@0: gl.bufferData(gl.ARRAY_BUFFER, coords, gl.STATIC_DRAW); michael@0: gl.enableVertexAttribArray(cache.positionLocation); michael@0: gl.vertexAttribPointer(cache.positionLocation, 2, gl.FLOAT, false, 0, 0); michael@0: michael@0: var colorsBuffer = gl.createBuffer(); michael@0: gl.bindBuffer(gl.ARRAY_BUFFER, colorsBuffer); michael@0: gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW); michael@0: gl.enableVertexAttribArray(cache.colorLocation); michael@0: gl.vertexAttribPointer(cache.colorLocation, 3, gl.UNSIGNED_BYTE, false, michael@0: 0, 0); michael@0: michael@0: gl.uniform2f(cache.scaleLocation, context.scaleX, context.scaleY); michael@0: gl.uniform2f(cache.offsetLocation, context.offsetX, context.offsetY); michael@0: michael@0: gl.drawArrays(gl.TRIANGLES, 0, count); michael@0: michael@0: gl.flush(); michael@0: michael@0: gl.deleteBuffer(coordsBuffer); michael@0: gl.deleteBuffer(colorsBuffer); michael@0: michael@0: return canvas; michael@0: } michael@0: michael@0: function cleanup() { michael@0: smaskCache = null; michael@0: figuresCache = null; michael@0: } michael@0: michael@0: return { michael@0: get isEnabled() { michael@0: if (PDFJS.disableWebGL) { michael@0: return false; michael@0: } michael@0: var enabled = false; michael@0: try { michael@0: generageGL(); michael@0: enabled = !!currentGL; michael@0: } catch (e) { } michael@0: return shadow(this, 'isEnabled', enabled); michael@0: }, michael@0: composeSMask: composeSMask, michael@0: drawFigures: drawFigures, michael@0: clear: cleanup michael@0: }; michael@0: })(); michael@0: michael@0: michael@0: var ShadingIRs = {}; michael@0: michael@0: ShadingIRs.RadialAxial = { michael@0: fromIR: function RadialAxial_fromIR(raw) { michael@0: var type = raw[1]; michael@0: var colorStops = raw[2]; michael@0: var p0 = raw[3]; michael@0: var p1 = raw[4]; michael@0: var r0 = raw[5]; michael@0: var r1 = raw[6]; michael@0: return { michael@0: type: 'Pattern', michael@0: getPattern: function RadialAxial_getPattern(ctx) { michael@0: var grad; michael@0: if (type === 'axial') { michael@0: grad = ctx.createLinearGradient(p0[0], p0[1], p1[0], p1[1]); michael@0: } else if (type === 'radial') { michael@0: grad = ctx.createRadialGradient(p0[0], p0[1], r0, p1[0], p1[1], r1); michael@0: } michael@0: michael@0: for (var i = 0, ii = colorStops.length; i < ii; ++i) { michael@0: var c = colorStops[i]; michael@0: grad.addColorStop(c[0], c[1]); michael@0: } michael@0: return grad; michael@0: } michael@0: }; michael@0: } michael@0: }; michael@0: michael@0: var createMeshCanvas = (function createMeshCanvasClosure() { michael@0: function drawTriangle(data, context, p1, p2, p3, c1, c2, c3) { michael@0: // Very basic Gouraud-shaded triangle rasterization algorithm. michael@0: var coords = context.coords, colors = context.colors; michael@0: var bytes = data.data, rowSize = data.width * 4; michael@0: var tmp; michael@0: if (coords[p1 + 1] > coords[p2 + 1]) { michael@0: tmp = p1; p1 = p2; p2 = tmp; tmp = c1; c1 = c2; c2 = tmp; michael@0: } michael@0: if (coords[p2 + 1] > coords[p3 + 1]) { michael@0: tmp = p2; p2 = p3; p3 = tmp; tmp = c2; c2 = c3; c3 = tmp; michael@0: } michael@0: if (coords[p1 + 1] > coords[p2 + 1]) { michael@0: tmp = p1; p1 = p2; p2 = tmp; tmp = c1; c1 = c2; c2 = tmp; michael@0: } michael@0: var x1 = (coords[p1] + context.offsetX) * context.scaleX; michael@0: var y1 = (coords[p1 + 1] + context.offsetY) * context.scaleY; michael@0: var x2 = (coords[p2] + context.offsetX) * context.scaleX; michael@0: var y2 = (coords[p2 + 1] + context.offsetY) * context.scaleY; michael@0: var x3 = (coords[p3] + context.offsetX) * context.scaleX; michael@0: var y3 = (coords[p3 + 1] + context.offsetY) * context.scaleY; michael@0: if (y1 >= y3) { michael@0: return; michael@0: } michael@0: var c1r = colors[c1], c1g = colors[c1 + 1], c1b = colors[c1 + 2]; michael@0: var c2r = colors[c2], c2g = colors[c2 + 1], c2b = colors[c2 + 2]; michael@0: var c3r = colors[c3], c3g = colors[c3 + 1], c3b = colors[c3 + 2]; michael@0: michael@0: var minY = Math.round(y1), maxY = Math.round(y3); michael@0: var xa, car, cag, cab; michael@0: var xb, cbr, cbg, cbb; michael@0: var k; michael@0: for (var y = minY; y <= maxY; y++) { michael@0: if (y < y2) { michael@0: k = y < y1 ? 0 : y1 === y2 ? 1 : (y1 - y) / (y1 - y2); michael@0: xa = x1 - (x1 - x2) * k; michael@0: car = c1r - (c1r - c2r) * k; michael@0: cag = c1g - (c1g - c2g) * k; michael@0: cab = c1b - (c1b - c2b) * k; michael@0: } else { michael@0: k = y > y3 ? 1 : y2 === y3 ? 0 : (y2 - y) / (y2 - y3); michael@0: xa = x2 - (x2 - x3) * k; michael@0: car = c2r - (c2r - c3r) * k; michael@0: cag = c2g - (c2g - c3g) * k; michael@0: cab = c2b - (c2b - c3b) * k; michael@0: } michael@0: k = y < y1 ? 0 : y > y3 ? 1 : (y1 - y) / (y1 - y3); michael@0: xb = x1 - (x1 - x3) * k; michael@0: cbr = c1r - (c1r - c3r) * k; michael@0: cbg = c1g - (c1g - c3g) * k; michael@0: cbb = c1b - (c1b - c3b) * k; michael@0: var x1_ = Math.round(Math.min(xa, xb)); michael@0: var x2_ = Math.round(Math.max(xa, xb)); michael@0: var j = rowSize * y + x1_ * 4; michael@0: for (var x = x1_; x <= x2_; x++) { michael@0: k = (xa - x) / (xa - xb); michael@0: k = k < 0 ? 0 : k > 1 ? 1 : k; michael@0: bytes[j++] = (car - (car - cbr) * k) | 0; michael@0: bytes[j++] = (cag - (cag - cbg) * k) | 0; michael@0: bytes[j++] = (cab - (cab - cbb) * k) | 0; michael@0: bytes[j++] = 255; michael@0: } michael@0: } michael@0: } michael@0: michael@0: function drawFigure(data, figure, context) { michael@0: var ps = figure.coords; michael@0: var cs = figure.colors; michael@0: var i, ii; michael@0: switch (figure.type) { michael@0: case 'lattice': michael@0: var verticesPerRow = figure.verticesPerRow; michael@0: var rows = Math.floor(ps.length / verticesPerRow) - 1; michael@0: var cols = verticesPerRow - 1; michael@0: for (i = 0; i < rows; i++) { michael@0: var q = i * verticesPerRow; michael@0: for (var j = 0; j < cols; j++, q++) { michael@0: drawTriangle(data, context, michael@0: ps[q], ps[q + 1], ps[q + verticesPerRow], michael@0: cs[q], cs[q + 1], cs[q + verticesPerRow]); michael@0: drawTriangle(data, context, michael@0: ps[q + verticesPerRow + 1], ps[q + 1], ps[q + verticesPerRow], michael@0: cs[q + verticesPerRow + 1], cs[q + 1], cs[q + verticesPerRow]); michael@0: } michael@0: } michael@0: break; michael@0: case 'triangles': michael@0: for (i = 0, ii = ps.length; i < ii; i += 3) { michael@0: drawTriangle(data, context, michael@0: ps[i], ps[i + 1], ps[i + 2], michael@0: cs[i], cs[i + 1], cs[i + 2]); michael@0: } michael@0: break; michael@0: default: michael@0: error('illigal figure'); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: function createMeshCanvas(bounds, combinesScale, coords, colors, figures, michael@0: backgroundColor) { michael@0: // we will increase scale on some weird factor to let antialiasing take michael@0: // care of "rough" edges michael@0: var EXPECTED_SCALE = 1.1; michael@0: // MAX_PATTERN_SIZE is used to avoid OOM situation. michael@0: var MAX_PATTERN_SIZE = 3000; // 10in @ 300dpi shall be enough michael@0: michael@0: var offsetX = Math.floor(bounds[0]); michael@0: var offsetY = Math.floor(bounds[1]); michael@0: var boundsWidth = Math.ceil(bounds[2]) - offsetX; michael@0: var boundsHeight = Math.ceil(bounds[3]) - offsetY; michael@0: michael@0: var width = Math.min(Math.ceil(Math.abs(boundsWidth * combinesScale[0] * michael@0: EXPECTED_SCALE)), MAX_PATTERN_SIZE); michael@0: var height = Math.min(Math.ceil(Math.abs(boundsHeight * combinesScale[1] * michael@0: EXPECTED_SCALE)), MAX_PATTERN_SIZE); michael@0: var scaleX = boundsWidth / width; michael@0: var scaleY = boundsHeight / height; michael@0: michael@0: var context = { michael@0: coords: coords, michael@0: colors: colors, michael@0: offsetX: -offsetX, michael@0: offsetY: -offsetY, michael@0: scaleX: 1 / scaleX, michael@0: scaleY: 1 / scaleY michael@0: }; michael@0: michael@0: var canvas, tmpCanvas, i, ii; michael@0: if (WebGLUtils.isEnabled) { michael@0: canvas = WebGLUtils.drawFigures(width, height, backgroundColor, michael@0: figures, context); michael@0: michael@0: // https://bugzilla.mozilla.org/show_bug.cgi?id=972126 michael@0: tmpCanvas = CachedCanvases.getCanvas('mesh', width, height, false); michael@0: tmpCanvas.context.drawImage(canvas, 0, 0); michael@0: canvas = tmpCanvas.canvas; michael@0: } else { michael@0: tmpCanvas = CachedCanvases.getCanvas('mesh', width, height, false); michael@0: var tmpCtx = tmpCanvas.context; michael@0: michael@0: var data = tmpCtx.createImageData(width, height); michael@0: if (backgroundColor) { michael@0: var bytes = data.data; michael@0: for (i = 0, ii = bytes.length; i < ii; i += 4) { michael@0: bytes[i] = backgroundColor[0]; michael@0: bytes[i + 1] = backgroundColor[1]; michael@0: bytes[i + 2] = backgroundColor[2]; michael@0: bytes[i + 3] = 255; michael@0: } michael@0: } michael@0: for (i = 0; i < figures.length; i++) { michael@0: drawFigure(data, figures[i], context); michael@0: } michael@0: tmpCtx.putImageData(data, 0, 0); michael@0: canvas = tmpCanvas.canvas; michael@0: } michael@0: michael@0: return {canvas: canvas, offsetX: offsetX, offsetY: offsetY, michael@0: scaleX: scaleX, scaleY: scaleY}; michael@0: } michael@0: return createMeshCanvas; michael@0: })(); michael@0: michael@0: ShadingIRs.Mesh = { michael@0: fromIR: function Mesh_fromIR(raw) { michael@0: //var type = raw[1]; michael@0: var coords = raw[2]; michael@0: var colors = raw[3]; michael@0: var figures = raw[4]; michael@0: var bounds = raw[5]; michael@0: var matrix = raw[6]; michael@0: //var bbox = raw[7]; michael@0: var background = raw[8]; michael@0: return { michael@0: type: 'Pattern', michael@0: getPattern: function Mesh_getPattern(ctx, owner, shadingFill) { michael@0: var combinedScale; michael@0: // Obtain scale from matrix and current transformation matrix. michael@0: if (shadingFill) { michael@0: combinedScale = Util.singularValueDecompose2dScale( michael@0: ctx.mozCurrentTransform); michael@0: } else { michael@0: var matrixScale = Util.singularValueDecompose2dScale(matrix); michael@0: var curMatrixScale = Util.singularValueDecompose2dScale( michael@0: owner.baseTransform); michael@0: combinedScale = [matrixScale[0] * curMatrixScale[0], michael@0: matrixScale[1] * curMatrixScale[1]]; michael@0: } michael@0: michael@0: michael@0: // Rasterizing on the main thread since sending/queue large canvases michael@0: // might cause OOM. michael@0: var temporaryPatternCanvas = createMeshCanvas(bounds, combinedScale, michael@0: coords, colors, figures, shadingFill ? null : background); michael@0: michael@0: if (!shadingFill) { michael@0: ctx.setTransform.apply(ctx, owner.baseTransform); michael@0: if (matrix) { michael@0: ctx.transform.apply(ctx, matrix); michael@0: } michael@0: } michael@0: michael@0: ctx.translate(temporaryPatternCanvas.offsetX, michael@0: temporaryPatternCanvas.offsetY); michael@0: ctx.scale(temporaryPatternCanvas.scaleX, michael@0: temporaryPatternCanvas.scaleY); michael@0: michael@0: return ctx.createPattern(temporaryPatternCanvas.canvas, 'no-repeat'); michael@0: } michael@0: }; michael@0: } michael@0: }; michael@0: michael@0: ShadingIRs.Dummy = { michael@0: fromIR: function Dummy_fromIR() { michael@0: return { michael@0: type: 'Pattern', michael@0: getPattern: function Dummy_fromIR_getPattern() { michael@0: return 'hotpink'; michael@0: } michael@0: }; michael@0: } michael@0: }; michael@0: michael@0: function getShadingPatternFromIR(raw) { michael@0: var shadingIR = ShadingIRs[raw[0]]; michael@0: if (!shadingIR) { michael@0: error('Unknown IR type: ' + raw[0]); michael@0: } michael@0: return shadingIR.fromIR(raw); michael@0: } michael@0: michael@0: var TilingPattern = (function TilingPatternClosure() { michael@0: var PaintType = { michael@0: COLORED: 1, michael@0: UNCOLORED: 2 michael@0: }; michael@0: michael@0: var MAX_PATTERN_SIZE = 3000; // 10in @ 300dpi shall be enough michael@0: michael@0: function TilingPattern(IR, color, ctx, objs, commonObjs, baseTransform) { michael@0: this.name = IR[1][0].name; michael@0: this.operatorList = IR[2]; michael@0: this.matrix = IR[3] || [1, 0, 0, 1, 0, 0]; michael@0: this.bbox = IR[4]; michael@0: this.xstep = IR[5]; michael@0: this.ystep = IR[6]; michael@0: this.paintType = IR[7]; michael@0: this.tilingType = IR[8]; michael@0: this.color = color; michael@0: this.objs = objs; michael@0: this.commonObjs = commonObjs; michael@0: this.baseTransform = baseTransform; michael@0: this.type = 'Pattern'; michael@0: this.ctx = ctx; michael@0: } michael@0: michael@0: TilingPattern.prototype = { michael@0: createPatternCanvas: function TilinPattern_createPatternCanvas(owner) { michael@0: var operatorList = this.operatorList; michael@0: var bbox = this.bbox; michael@0: var xstep = this.xstep; michael@0: var ystep = this.ystep; michael@0: var paintType = this.paintType; michael@0: var tilingType = this.tilingType; michael@0: var color = this.color; michael@0: var objs = this.objs; michael@0: var commonObjs = this.commonObjs; michael@0: michael@0: info('TilingType: ' + tilingType); michael@0: michael@0: var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3]; michael@0: michael@0: var topLeft = [x0, y0]; michael@0: // we want the canvas to be as large as the step size michael@0: var botRight = [x0 + xstep, y0 + ystep]; michael@0: michael@0: var width = botRight[0] - topLeft[0]; michael@0: var height = botRight[1] - topLeft[1]; michael@0: michael@0: // Obtain scale from matrix and current transformation matrix. michael@0: var matrixScale = Util.singularValueDecompose2dScale(this.matrix); michael@0: var curMatrixScale = Util.singularValueDecompose2dScale( michael@0: this.baseTransform); michael@0: var combinedScale = [matrixScale[0] * curMatrixScale[0], michael@0: matrixScale[1] * curMatrixScale[1]]; michael@0: michael@0: // MAX_PATTERN_SIZE is used to avoid OOM situation. michael@0: // Use width and height values that are as close as possible to the end michael@0: // result when the pattern is used. Too low value makes the pattern look michael@0: // blurry. Too large value makes it look too crispy. michael@0: width = Math.min(Math.ceil(Math.abs(width * combinedScale[0])), michael@0: MAX_PATTERN_SIZE); michael@0: michael@0: height = Math.min(Math.ceil(Math.abs(height * combinedScale[1])), michael@0: MAX_PATTERN_SIZE); michael@0: michael@0: var tmpCanvas = CachedCanvases.getCanvas('pattern', width, height, true); michael@0: var tmpCtx = tmpCanvas.context; michael@0: var graphics = new CanvasGraphics(tmpCtx, commonObjs, objs); michael@0: graphics.groupLevel = owner.groupLevel; michael@0: michael@0: this.setFillAndStrokeStyleToContext(tmpCtx, paintType, color); michael@0: michael@0: this.setScale(width, height, xstep, ystep); michael@0: this.transformToScale(graphics); michael@0: michael@0: // transform coordinates to pattern space michael@0: var tmpTranslate = [1, 0, 0, 1, -topLeft[0], -topLeft[1]]; michael@0: graphics.transform.apply(graphics, tmpTranslate); michael@0: michael@0: this.clipBbox(graphics, bbox, x0, y0, x1, y1); michael@0: michael@0: graphics.executeOperatorList(operatorList); michael@0: return tmpCanvas.canvas; michael@0: }, michael@0: michael@0: setScale: function TilingPattern_setScale(width, height, xstep, ystep) { michael@0: this.scale = [width / xstep, height / ystep]; michael@0: }, michael@0: michael@0: transformToScale: function TilingPattern_transformToScale(graphics) { michael@0: var scale = this.scale; michael@0: var tmpScale = [scale[0], 0, 0, scale[1], 0, 0]; michael@0: graphics.transform.apply(graphics, tmpScale); michael@0: }, michael@0: michael@0: scaleToContext: function TilingPattern_scaleToContext() { michael@0: var scale = this.scale; michael@0: this.ctx.scale(1 / scale[0], 1 / scale[1]); michael@0: }, michael@0: michael@0: clipBbox: function clipBbox(graphics, bbox, x0, y0, x1, y1) { michael@0: if (bbox && isArray(bbox) && 4 == bbox.length) { michael@0: var bboxWidth = x1 - x0; michael@0: var bboxHeight = y1 - y0; michael@0: graphics.rectangle(x0, y0, bboxWidth, bboxHeight); michael@0: graphics.clip(); michael@0: graphics.endPath(); michael@0: } michael@0: }, michael@0: michael@0: setFillAndStrokeStyleToContext: michael@0: function setFillAndStrokeStyleToContext(context, paintType, color) { michael@0: switch (paintType) { michael@0: case PaintType.COLORED: michael@0: var ctx = this.ctx; michael@0: context.fillStyle = ctx.fillStyle; michael@0: context.strokeStyle = ctx.strokeStyle; michael@0: break; michael@0: case PaintType.UNCOLORED: michael@0: var rgbColor = ColorSpace.singletons.rgb.getRgb(color, 0); michael@0: var cssColor = Util.makeCssRgb(rgbColor); michael@0: context.fillStyle = cssColor; michael@0: context.strokeStyle = cssColor; michael@0: break; michael@0: default: michael@0: error('Unsupported paint type: ' + paintType); michael@0: } michael@0: }, michael@0: michael@0: getPattern: function TilingPattern_getPattern(ctx, owner) { michael@0: var temporaryPatternCanvas = this.createPatternCanvas(owner); michael@0: michael@0: ctx = this.ctx; michael@0: ctx.setTransform.apply(ctx, this.baseTransform); michael@0: ctx.transform.apply(ctx, this.matrix); michael@0: this.scaleToContext(); michael@0: michael@0: return ctx.createPattern(temporaryPatternCanvas, 'repeat'); michael@0: } michael@0: }; michael@0: michael@0: return TilingPattern; michael@0: })(); michael@0: michael@0: michael@0: PDFJS.disableFontFace = false; michael@0: michael@0: var FontLoader = { michael@0: insertRule: function fontLoaderInsertRule(rule) { michael@0: var styleElement = document.getElementById('PDFJS_FONT_STYLE_TAG'); michael@0: if (!styleElement) { michael@0: styleElement = document.createElement('style'); michael@0: styleElement.id = 'PDFJS_FONT_STYLE_TAG'; michael@0: document.documentElement.getElementsByTagName('head')[0].appendChild( michael@0: styleElement); michael@0: } michael@0: michael@0: var styleSheet = styleElement.sheet; michael@0: styleSheet.insertRule(rule, styleSheet.cssRules.length); michael@0: }, michael@0: michael@0: clear: function fontLoaderClear() { michael@0: var styleElement = document.getElementById('PDFJS_FONT_STYLE_TAG'); michael@0: if (styleElement) { michael@0: styleElement.parentNode.removeChild(styleElement); michael@0: } michael@0: }, michael@0: bind: function fontLoaderBind(fonts, callback) { michael@0: assert(!isWorker, 'bind() shall be called from main thread'); michael@0: michael@0: for (var i = 0, ii = fonts.length; i < ii; i++) { michael@0: var font = fonts[i]; michael@0: if (font.attached) { michael@0: continue; michael@0: } michael@0: michael@0: font.attached = true; michael@0: font.bindDOM() michael@0: } michael@0: michael@0: setTimeout(callback); michael@0: } michael@0: }; michael@0: michael@0: var FontFace = (function FontFaceClosure() { michael@0: function FontFace(name, file, properties) { michael@0: this.compiledGlyphs = {}; michael@0: if (arguments.length === 1) { michael@0: // importing translated data michael@0: var data = arguments[0]; michael@0: for (var i in data) { michael@0: this[i] = data[i]; michael@0: } michael@0: return; michael@0: } michael@0: } michael@0: FontFace.prototype = { michael@0: bindDOM: function FontFace_bindDOM() { michael@0: if (!this.data) { michael@0: return null; michael@0: } michael@0: michael@0: if (PDFJS.disableFontFace) { michael@0: this.disableFontFace = true; michael@0: return null; michael@0: } michael@0: michael@0: var data = bytesToString(new Uint8Array(this.data)); michael@0: var fontName = this.loadedName; michael@0: michael@0: // Add the font-face rule to the document michael@0: var url = ('url(data:' + this.mimetype + ';base64,' + michael@0: window.btoa(data) + ');'); michael@0: var rule = '@font-face { font-family:"' + fontName + '";src:' + url + '}'; michael@0: FontLoader.insertRule(rule); michael@0: michael@0: if (PDFJS.pdfBug && 'FontInspector' in globalScope && michael@0: globalScope['FontInspector'].enabled) { michael@0: globalScope['FontInspector'].fontAdded(this, url); michael@0: } michael@0: michael@0: return rule; michael@0: }, michael@0: michael@0: getPathGenerator: function (objs, character) { michael@0: if (!(character in this.compiledGlyphs)) { michael@0: var js = objs.get(this.loadedName + '_path_' + character); michael@0: /*jshint -W054 */ michael@0: this.compiledGlyphs[character] = new Function('c', 'size', js); michael@0: } michael@0: return this.compiledGlyphs[character]; michael@0: } michael@0: }; michael@0: return FontFace; michael@0: })(); michael@0: michael@0: michael@0: }).call((typeof window === 'undefined') ? this : window); michael@0: michael@0: michael@0: