Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
3 /* Copyright 2012 Mozilla Foundation
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 /*jshint globalstrict: false */
19 // Initializing PDFJS global object (if still undefined)
20 if (typeof PDFJS === 'undefined') {
21 (typeof window !== 'undefined' ? window : this).PDFJS = {};
22 }
24 PDFJS.version = '1.0.68';
25 PDFJS.build = 'ead4cbf';
27 (function pdfjsWrapper() {
28 // Use strict in our context only - users might not want it
29 'use strict';
31 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
32 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
33 /* Copyright 2012 Mozilla Foundation
34 *
35 * Licensed under the Apache License, Version 2.0 (the "License");
36 * you may not use this file except in compliance with the License.
37 * You may obtain a copy of the License at
38 *
39 * http://www.apache.org/licenses/LICENSE-2.0
40 *
41 * Unless required by applicable law or agreed to in writing, software
42 * distributed under the License is distributed on an "AS IS" BASIS,
43 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
44 * See the License for the specific language governing permissions and
45 * limitations under the License.
46 */
47 /* globals Cmd, ColorSpace, Dict, MozBlobBuilder, Name, PDFJS, Ref, URL,
48 Promise */
50 'use strict';
52 var globalScope = (typeof window === 'undefined') ? this : window;
54 var isWorker = (typeof window == 'undefined');
56 var FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0];
58 var TextRenderingMode = {
59 FILL: 0,
60 STROKE: 1,
61 FILL_STROKE: 2,
62 INVISIBLE: 3,
63 FILL_ADD_TO_PATH: 4,
64 STROKE_ADD_TO_PATH: 5,
65 FILL_STROKE_ADD_TO_PATH: 6,
66 ADD_TO_PATH: 7,
67 FILL_STROKE_MASK: 3,
68 ADD_TO_PATH_FLAG: 4
69 };
71 var ImageKind = {
72 GRAYSCALE_1BPP: 1,
73 RGB_24BPP: 2,
74 RGBA_32BPP: 3
75 };
77 // The global PDFJS object exposes the API
78 // In production, it will be declared outside a global wrapper
79 // In development, it will be declared here
80 if (!globalScope.PDFJS) {
81 globalScope.PDFJS = {};
82 }
84 globalScope.PDFJS.pdfBug = false;
86 PDFJS.VERBOSITY_LEVELS = {
87 errors: 0,
88 warnings: 1,
89 infos: 5
90 };
92 // All the possible operations for an operator list.
93 var OPS = PDFJS.OPS = {
94 // Intentionally start from 1 so it is easy to spot bad operators that will be
95 // 0's.
96 dependency: 1,
97 setLineWidth: 2,
98 setLineCap: 3,
99 setLineJoin: 4,
100 setMiterLimit: 5,
101 setDash: 6,
102 setRenderingIntent: 7,
103 setFlatness: 8,
104 setGState: 9,
105 save: 10,
106 restore: 11,
107 transform: 12,
108 moveTo: 13,
109 lineTo: 14,
110 curveTo: 15,
111 curveTo2: 16,
112 curveTo3: 17,
113 closePath: 18,
114 rectangle: 19,
115 stroke: 20,
116 closeStroke: 21,
117 fill: 22,
118 eoFill: 23,
119 fillStroke: 24,
120 eoFillStroke: 25,
121 closeFillStroke: 26,
122 closeEOFillStroke: 27,
123 endPath: 28,
124 clip: 29,
125 eoClip: 30,
126 beginText: 31,
127 endText: 32,
128 setCharSpacing: 33,
129 setWordSpacing: 34,
130 setHScale: 35,
131 setLeading: 36,
132 setFont: 37,
133 setTextRenderingMode: 38,
134 setTextRise: 39,
135 moveText: 40,
136 setLeadingMoveText: 41,
137 setTextMatrix: 42,
138 nextLine: 43,
139 showText: 44,
140 showSpacedText: 45,
141 nextLineShowText: 46,
142 nextLineSetSpacingShowText: 47,
143 setCharWidth: 48,
144 setCharWidthAndBounds: 49,
145 setStrokeColorSpace: 50,
146 setFillColorSpace: 51,
147 setStrokeColor: 52,
148 setStrokeColorN: 53,
149 setFillColor: 54,
150 setFillColorN: 55,
151 setStrokeGray: 56,
152 setFillGray: 57,
153 setStrokeRGBColor: 58,
154 setFillRGBColor: 59,
155 setStrokeCMYKColor: 60,
156 setFillCMYKColor: 61,
157 shadingFill: 62,
158 beginInlineImage: 63,
159 beginImageData: 64,
160 endInlineImage: 65,
161 paintXObject: 66,
162 markPoint: 67,
163 markPointProps: 68,
164 beginMarkedContent: 69,
165 beginMarkedContentProps: 70,
166 endMarkedContent: 71,
167 beginCompat: 72,
168 endCompat: 73,
169 paintFormXObjectBegin: 74,
170 paintFormXObjectEnd: 75,
171 beginGroup: 76,
172 endGroup: 77,
173 beginAnnotations: 78,
174 endAnnotations: 79,
175 beginAnnotation: 80,
176 endAnnotation: 81,
177 paintJpegXObject: 82,
178 paintImageMaskXObject: 83,
179 paintImageMaskXObjectGroup: 84,
180 paintImageXObject: 85,
181 paintInlineImageXObject: 86,
182 paintInlineImageXObjectGroup: 87,
183 paintImageXObjectRepeat: 88,
184 paintImageMaskXObjectRepeat: 89,
185 paintSolidColorImageMask: 90
186 };
188 // A notice for devs. These are good for things that are helpful to devs, such
189 // as warning that Workers were disabled, which is important to devs but not
190 // end users.
191 function info(msg) {
192 if (PDFJS.verbosity >= PDFJS.VERBOSITY_LEVELS.infos) {
193 console.log('Info: ' + msg);
194 }
195 }
197 // Non-fatal warnings.
198 function warn(msg) {
199 if (PDFJS.verbosity >= PDFJS.VERBOSITY_LEVELS.warnings) {
200 console.log('Warning: ' + msg);
201 }
202 }
204 // Fatal errors that should trigger the fallback UI and halt execution by
205 // throwing an exception.
206 function error(msg) {
207 // If multiple arguments were passed, pass them all to the log function.
208 if (arguments.length > 1) {
209 var logArguments = ['Error:'];
210 logArguments.push.apply(logArguments, arguments);
211 console.log.apply(console, logArguments);
212 // Join the arguments into a single string for the lines below.
213 msg = [].join.call(arguments, ' ');
214 } else {
215 console.log('Error: ' + msg);
216 }
217 console.log(backtrace());
218 UnsupportedManager.notify(UNSUPPORTED_FEATURES.unknown);
219 throw new Error(msg);
220 }
222 function backtrace() {
223 try {
224 throw new Error();
225 } catch (e) {
226 return e.stack ? e.stack.split('\n').slice(2).join('\n') : '';
227 }
228 }
230 function assert(cond, msg) {
231 if (!cond) {
232 error(msg);
233 }
234 }
236 var UNSUPPORTED_FEATURES = PDFJS.UNSUPPORTED_FEATURES = {
237 unknown: 'unknown',
238 forms: 'forms',
239 javaScript: 'javaScript',
240 smask: 'smask',
241 shadingPattern: 'shadingPattern',
242 font: 'font'
243 };
245 var UnsupportedManager = PDFJS.UnsupportedManager =
246 (function UnsupportedManagerClosure() {
247 var listeners = [];
248 return {
249 listen: function (cb) {
250 listeners.push(cb);
251 },
252 notify: function (featureId) {
253 warn('Unsupported feature "' + featureId + '"');
254 for (var i = 0, ii = listeners.length; i < ii; i++) {
255 listeners[i](featureId);
256 }
257 }
258 };
259 })();
261 // Combines two URLs. The baseUrl shall be absolute URL. If the url is an
262 // absolute URL, it will be returned as is.
263 function combineUrl(baseUrl, url) {
264 if (!url) {
265 return baseUrl;
266 }
267 if (/^[a-z][a-z0-9+\-.]*:/i.test(url)) {
268 return url;
269 }
270 var i;
271 if (url.charAt(0) == '/') {
272 // absolute path
273 i = baseUrl.indexOf('://');
274 if (url.charAt(1) === '/') {
275 ++i;
276 } else {
277 i = baseUrl.indexOf('/', i + 3);
278 }
279 return baseUrl.substring(0, i) + url;
280 } else {
281 // relative path
282 var pathLength = baseUrl.length;
283 i = baseUrl.lastIndexOf('#');
284 pathLength = i >= 0 ? i : pathLength;
285 i = baseUrl.lastIndexOf('?', pathLength);
286 pathLength = i >= 0 ? i : pathLength;
287 var prefixLength = baseUrl.lastIndexOf('/', pathLength);
288 return baseUrl.substring(0, prefixLength + 1) + url;
289 }
290 }
292 // Validates if URL is safe and allowed, e.g. to avoid XSS.
293 function isValidUrl(url, allowRelative) {
294 if (!url) {
295 return false;
296 }
297 // RFC 3986 (http://tools.ietf.org/html/rfc3986#section-3.1)
298 // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
299 var protocol = /^[a-z][a-z0-9+\-.]*(?=:)/i.exec(url);
300 if (!protocol) {
301 return allowRelative;
302 }
303 protocol = protocol[0].toLowerCase();
304 switch (protocol) {
305 case 'http':
306 case 'https':
307 case 'ftp':
308 case 'mailto':
309 return true;
310 default:
311 return false;
312 }
313 }
314 PDFJS.isValidUrl = isValidUrl;
316 function shadow(obj, prop, value) {
317 Object.defineProperty(obj, prop, { value: value,
318 enumerable: true,
319 configurable: true,
320 writable: false });
321 return value;
322 }
324 var PasswordResponses = PDFJS.PasswordResponses = {
325 NEED_PASSWORD: 1,
326 INCORRECT_PASSWORD: 2
327 };
329 var PasswordException = (function PasswordExceptionClosure() {
330 function PasswordException(msg, code) {
331 this.name = 'PasswordException';
332 this.message = msg;
333 this.code = code;
334 }
336 PasswordException.prototype = new Error();
337 PasswordException.constructor = PasswordException;
339 return PasswordException;
340 })();
342 var UnknownErrorException = (function UnknownErrorExceptionClosure() {
343 function UnknownErrorException(msg, details) {
344 this.name = 'UnknownErrorException';
345 this.message = msg;
346 this.details = details;
347 }
349 UnknownErrorException.prototype = new Error();
350 UnknownErrorException.constructor = UnknownErrorException;
352 return UnknownErrorException;
353 })();
355 var InvalidPDFException = (function InvalidPDFExceptionClosure() {
356 function InvalidPDFException(msg) {
357 this.name = 'InvalidPDFException';
358 this.message = msg;
359 }
361 InvalidPDFException.prototype = new Error();
362 InvalidPDFException.constructor = InvalidPDFException;
364 return InvalidPDFException;
365 })();
367 var MissingPDFException = (function MissingPDFExceptionClosure() {
368 function MissingPDFException(msg) {
369 this.name = 'MissingPDFException';
370 this.message = msg;
371 }
373 MissingPDFException.prototype = new Error();
374 MissingPDFException.constructor = MissingPDFException;
376 return MissingPDFException;
377 })();
379 var NotImplementedException = (function NotImplementedExceptionClosure() {
380 function NotImplementedException(msg) {
381 this.message = msg;
382 }
384 NotImplementedException.prototype = new Error();
385 NotImplementedException.prototype.name = 'NotImplementedException';
386 NotImplementedException.constructor = NotImplementedException;
388 return NotImplementedException;
389 })();
391 var MissingDataException = (function MissingDataExceptionClosure() {
392 function MissingDataException(begin, end) {
393 this.begin = begin;
394 this.end = end;
395 this.message = 'Missing data [' + begin + ', ' + end + ')';
396 }
398 MissingDataException.prototype = new Error();
399 MissingDataException.prototype.name = 'MissingDataException';
400 MissingDataException.constructor = MissingDataException;
402 return MissingDataException;
403 })();
405 var XRefParseException = (function XRefParseExceptionClosure() {
406 function XRefParseException(msg) {
407 this.message = msg;
408 }
410 XRefParseException.prototype = new Error();
411 XRefParseException.prototype.name = 'XRefParseException';
412 XRefParseException.constructor = XRefParseException;
414 return XRefParseException;
415 })();
418 function bytesToString(bytes) {
419 var length = bytes.length;
420 var MAX_ARGUMENT_COUNT = 8192;
421 if (length < MAX_ARGUMENT_COUNT) {
422 return String.fromCharCode.apply(null, bytes);
423 }
424 var strBuf = [];
425 for (var i = 0; i < length; i += MAX_ARGUMENT_COUNT) {
426 var chunkEnd = Math.min(i + MAX_ARGUMENT_COUNT, length);
427 var chunk = bytes.subarray(i, chunkEnd);
428 strBuf.push(String.fromCharCode.apply(null, chunk));
429 }
430 return strBuf.join('');
431 }
433 function stringToArray(str) {
434 var length = str.length;
435 var array = [];
436 for (var i = 0; i < length; ++i) {
437 array[i] = str.charCodeAt(i);
438 }
439 return array;
440 }
442 function stringToBytes(str) {
443 var length = str.length;
444 var bytes = new Uint8Array(length);
445 for (var i = 0; i < length; ++i) {
446 bytes[i] = str.charCodeAt(i) & 0xFF;
447 }
448 return bytes;
449 }
451 function string32(value) {
452 return String.fromCharCode((value >> 24) & 0xff, (value >> 16) & 0xff,
453 (value >> 8) & 0xff, value & 0xff);
454 }
456 function log2(x) {
457 var n = 1, i = 0;
458 while (x > n) {
459 n <<= 1;
460 i++;
461 }
462 return i;
463 }
465 function readInt8(data, start) {
466 return (data[start] << 24) >> 24;
467 }
469 function readUint16(data, offset) {
470 return (data[offset] << 8) | data[offset + 1];
471 }
473 function readUint32(data, offset) {
474 return ((data[offset] << 24) | (data[offset + 1] << 16) |
475 (data[offset + 2] << 8) | data[offset + 3]) >>> 0;
476 }
478 // Lazy test the endianness of the platform
479 // NOTE: This will be 'true' for simulated TypedArrays
480 function isLittleEndian() {
481 var buffer8 = new Uint8Array(2);
482 buffer8[0] = 1;
483 var buffer16 = new Uint16Array(buffer8.buffer);
484 return (buffer16[0] === 1);
485 }
487 Object.defineProperty(PDFJS, 'isLittleEndian', {
488 configurable: true,
489 get: function PDFJS_isLittleEndian() {
490 return shadow(PDFJS, 'isLittleEndian', isLittleEndian());
491 }
492 });
494 PDFJS.hasCanvasTypedArrays = true;
496 var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
498 var Util = PDFJS.Util = (function UtilClosure() {
499 function Util() {}
501 Util.makeCssRgb = function Util_makeCssRgb(rgb) {
502 return 'rgb(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ')';
503 };
505 Util.makeCssCmyk = function Util_makeCssCmyk(cmyk) {
506 var rgb = ColorSpace.singletons.cmyk.getRgb(cmyk, 0);
507 return Util.makeCssRgb(rgb);
508 };
510 // Concatenates two transformation matrices together and returns the result.
511 Util.transform = function Util_transform(m1, m2) {
512 return [
513 m1[0] * m2[0] + m1[2] * m2[1],
514 m1[1] * m2[0] + m1[3] * m2[1],
515 m1[0] * m2[2] + m1[2] * m2[3],
516 m1[1] * m2[2] + m1[3] * m2[3],
517 m1[0] * m2[4] + m1[2] * m2[5] + m1[4],
518 m1[1] * m2[4] + m1[3] * m2[5] + m1[5]
519 ];
520 };
522 // For 2d affine transforms
523 Util.applyTransform = function Util_applyTransform(p, m) {
524 var xt = p[0] * m[0] + p[1] * m[2] + m[4];
525 var yt = p[0] * m[1] + p[1] * m[3] + m[5];
526 return [xt, yt];
527 };
529 Util.applyInverseTransform = function Util_applyInverseTransform(p, m) {
530 var d = m[0] * m[3] - m[1] * m[2];
531 var xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d;
532 var yt = (-p[0] * m[1] + p[1] * m[0] + m[4] * m[1] - m[5] * m[0]) / d;
533 return [xt, yt];
534 };
536 // Applies the transform to the rectangle and finds the minimum axially
537 // aligned bounding box.
538 Util.getAxialAlignedBoundingBox =
539 function Util_getAxialAlignedBoundingBox(r, m) {
541 var p1 = Util.applyTransform(r, m);
542 var p2 = Util.applyTransform(r.slice(2, 4), m);
543 var p3 = Util.applyTransform([r[0], r[3]], m);
544 var p4 = Util.applyTransform([r[2], r[1]], m);
545 return [
546 Math.min(p1[0], p2[0], p3[0], p4[0]),
547 Math.min(p1[1], p2[1], p3[1], p4[1]),
548 Math.max(p1[0], p2[0], p3[0], p4[0]),
549 Math.max(p1[1], p2[1], p3[1], p4[1])
550 ];
551 };
553 Util.inverseTransform = function Util_inverseTransform(m) {
554 var d = m[0] * m[3] - m[1] * m[2];
555 return [m[3] / d, -m[1] / d, -m[2] / d, m[0] / d,
556 (m[2] * m[5] - m[4] * m[3]) / d, (m[4] * m[1] - m[5] * m[0]) / d];
557 };
559 // Apply a generic 3d matrix M on a 3-vector v:
560 // | a b c | | X |
561 // | d e f | x | Y |
562 // | g h i | | Z |
563 // M is assumed to be serialized as [a,b,c,d,e,f,g,h,i],
564 // with v as [X,Y,Z]
565 Util.apply3dTransform = function Util_apply3dTransform(m, v) {
566 return [
567 m[0] * v[0] + m[1] * v[1] + m[2] * v[2],
568 m[3] * v[0] + m[4] * v[1] + m[5] * v[2],
569 m[6] * v[0] + m[7] * v[1] + m[8] * v[2]
570 ];
571 };
573 // This calculation uses Singular Value Decomposition.
574 // The SVD can be represented with formula A = USV. We are interested in the
575 // matrix S here because it represents the scale values.
576 Util.singularValueDecompose2dScale =
577 function Util_singularValueDecompose2dScale(m) {
579 var transpose = [m[0], m[2], m[1], m[3]];
581 // Multiply matrix m with its transpose.
582 var a = m[0] * transpose[0] + m[1] * transpose[2];
583 var b = m[0] * transpose[1] + m[1] * transpose[3];
584 var c = m[2] * transpose[0] + m[3] * transpose[2];
585 var d = m[2] * transpose[1] + m[3] * transpose[3];
587 // Solve the second degree polynomial to get roots.
588 var first = (a + d) / 2;
589 var second = Math.sqrt((a + d) * (a + d) - 4 * (a * d - c * b)) / 2;
590 var sx = first + second || 1;
591 var sy = first - second || 1;
593 // Scale values are the square roots of the eigenvalues.
594 return [Math.sqrt(sx), Math.sqrt(sy)];
595 };
597 // Normalize rectangle rect=[x1, y1, x2, y2] so that (x1,y1) < (x2,y2)
598 // For coordinate systems whose origin lies in the bottom-left, this
599 // means normalization to (BL,TR) ordering. For systems with origin in the
600 // top-left, this means (TL,BR) ordering.
601 Util.normalizeRect = function Util_normalizeRect(rect) {
602 var r = rect.slice(0); // clone rect
603 if (rect[0] > rect[2]) {
604 r[0] = rect[2];
605 r[2] = rect[0];
606 }
607 if (rect[1] > rect[3]) {
608 r[1] = rect[3];
609 r[3] = rect[1];
610 }
611 return r;
612 };
614 // Returns a rectangle [x1, y1, x2, y2] corresponding to the
615 // intersection of rect1 and rect2. If no intersection, returns 'false'
616 // The rectangle coordinates of rect1, rect2 should be [x1, y1, x2, y2]
617 Util.intersect = function Util_intersect(rect1, rect2) {
618 function compare(a, b) {
619 return a - b;
620 }
622 // Order points along the axes
623 var orderedX = [rect1[0], rect1[2], rect2[0], rect2[2]].sort(compare),
624 orderedY = [rect1[1], rect1[3], rect2[1], rect2[3]].sort(compare),
625 result = [];
627 rect1 = Util.normalizeRect(rect1);
628 rect2 = Util.normalizeRect(rect2);
630 // X: first and second points belong to different rectangles?
631 if ((orderedX[0] === rect1[0] && orderedX[1] === rect2[0]) ||
632 (orderedX[0] === rect2[0] && orderedX[1] === rect1[0])) {
633 // Intersection must be between second and third points
634 result[0] = orderedX[1];
635 result[2] = orderedX[2];
636 } else {
637 return false;
638 }
640 // Y: first and second points belong to different rectangles?
641 if ((orderedY[0] === rect1[1] && orderedY[1] === rect2[1]) ||
642 (orderedY[0] === rect2[1] && orderedY[1] === rect1[1])) {
643 // Intersection must be between second and third points
644 result[1] = orderedY[1];
645 result[3] = orderedY[2];
646 } else {
647 return false;
648 }
650 return result;
651 };
653 Util.sign = function Util_sign(num) {
654 return num < 0 ? -1 : 1;
655 };
657 // TODO(mack): Rename appendToArray
658 Util.concatenateToArray = function concatenateToArray(arr1, arr2) {
659 Array.prototype.push.apply(arr1, arr2);
660 };
662 Util.prependToArray = function concatenateToArray(arr1, arr2) {
663 Array.prototype.unshift.apply(arr1, arr2);
664 };
666 Util.extendObj = function extendObj(obj1, obj2) {
667 for (var key in obj2) {
668 obj1[key] = obj2[key];
669 }
670 };
672 Util.getInheritableProperty = function Util_getInheritableProperty(dict,
673 name) {
674 while (dict && !dict.has(name)) {
675 dict = dict.get('Parent');
676 }
677 if (!dict) {
678 return null;
679 }
680 return dict.get(name);
681 };
683 Util.inherit = function Util_inherit(sub, base, prototype) {
684 sub.prototype = Object.create(base.prototype);
685 sub.prototype.constructor = sub;
686 for (var prop in prototype) {
687 sub.prototype[prop] = prototype[prop];
688 }
689 };
691 Util.loadScript = function Util_loadScript(src, callback) {
692 var script = document.createElement('script');
693 var loaded = false;
694 script.setAttribute('src', src);
695 if (callback) {
696 script.onload = function() {
697 if (!loaded) {
698 callback();
699 }
700 loaded = true;
701 };
702 }
703 document.getElementsByTagName('head')[0].appendChild(script);
704 };
706 return Util;
707 })();
709 var PageViewport = PDFJS.PageViewport = (function PageViewportClosure() {
710 function PageViewport(viewBox, scale, rotation, offsetX, offsetY, dontFlip) {
711 this.viewBox = viewBox;
712 this.scale = scale;
713 this.rotation = rotation;
714 this.offsetX = offsetX;
715 this.offsetY = offsetY;
717 // creating transform to convert pdf coordinate system to the normal
718 // canvas like coordinates taking in account scale and rotation
719 var centerX = (viewBox[2] + viewBox[0]) / 2;
720 var centerY = (viewBox[3] + viewBox[1]) / 2;
721 var rotateA, rotateB, rotateC, rotateD;
722 rotation = rotation % 360;
723 rotation = rotation < 0 ? rotation + 360 : rotation;
724 switch (rotation) {
725 case 180:
726 rotateA = -1; rotateB = 0; rotateC = 0; rotateD = 1;
727 break;
728 case 90:
729 rotateA = 0; rotateB = 1; rotateC = 1; rotateD = 0;
730 break;
731 case 270:
732 rotateA = 0; rotateB = -1; rotateC = -1; rotateD = 0;
733 break;
734 //case 0:
735 default:
736 rotateA = 1; rotateB = 0; rotateC = 0; rotateD = -1;
737 break;
738 }
740 if (dontFlip) {
741 rotateC = -rotateC; rotateD = -rotateD;
742 }
744 var offsetCanvasX, offsetCanvasY;
745 var width, height;
746 if (rotateA === 0) {
747 offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX;
748 offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY;
749 width = Math.abs(viewBox[3] - viewBox[1]) * scale;
750 height = Math.abs(viewBox[2] - viewBox[0]) * scale;
751 } else {
752 offsetCanvasX = Math.abs(centerX - viewBox[0]) * scale + offsetX;
753 offsetCanvasY = Math.abs(centerY - viewBox[1]) * scale + offsetY;
754 width = Math.abs(viewBox[2] - viewBox[0]) * scale;
755 height = Math.abs(viewBox[3] - viewBox[1]) * scale;
756 }
757 // creating transform for the following operations:
758 // translate(-centerX, -centerY), rotate and flip vertically,
759 // scale, and translate(offsetCanvasX, offsetCanvasY)
760 this.transform = [
761 rotateA * scale,
762 rotateB * scale,
763 rotateC * scale,
764 rotateD * scale,
765 offsetCanvasX - rotateA * scale * centerX - rotateC * scale * centerY,
766 offsetCanvasY - rotateB * scale * centerX - rotateD * scale * centerY
767 ];
769 this.width = width;
770 this.height = height;
771 this.fontScale = scale;
772 }
773 PageViewport.prototype = {
774 clone: function PageViewPort_clone(args) {
775 args = args || {};
776 var scale = 'scale' in args ? args.scale : this.scale;
777 var rotation = 'rotation' in args ? args.rotation : this.rotation;
778 return new PageViewport(this.viewBox.slice(), scale, rotation,
779 this.offsetX, this.offsetY, args.dontFlip);
780 },
781 convertToViewportPoint: function PageViewport_convertToViewportPoint(x, y) {
782 return Util.applyTransform([x, y], this.transform);
783 },
784 convertToViewportRectangle:
785 function PageViewport_convertToViewportRectangle(rect) {
786 var tl = Util.applyTransform([rect[0], rect[1]], this.transform);
787 var br = Util.applyTransform([rect[2], rect[3]], this.transform);
788 return [tl[0], tl[1], br[0], br[1]];
789 },
790 convertToPdfPoint: function PageViewport_convertToPdfPoint(x, y) {
791 return Util.applyInverseTransform([x, y], this.transform);
792 }
793 };
794 return PageViewport;
795 })();
797 var PDFStringTranslateTable = [
798 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
799 0x2D8, 0x2C7, 0x2C6, 0x2D9, 0x2DD, 0x2DB, 0x2DA, 0x2DC, 0, 0, 0, 0, 0, 0, 0,
800 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,
801 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,
802 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,
803 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2022, 0x2020, 0x2021, 0x2026, 0x2014,
804 0x2013, 0x192, 0x2044, 0x2039, 0x203A, 0x2212, 0x2030, 0x201E, 0x201C,
805 0x201D, 0x2018, 0x2019, 0x201A, 0x2122, 0xFB01, 0xFB02, 0x141, 0x152, 0x160,
806 0x178, 0x17D, 0x131, 0x142, 0x153, 0x161, 0x17E, 0, 0x20AC
807 ];
809 function stringToPDFString(str) {
810 var i, n = str.length, strBuf = [];
811 if (str[0] === '\xFE' && str[1] === '\xFF') {
812 // UTF16BE BOM
813 for (i = 2; i < n; i += 2) {
814 strBuf.push(String.fromCharCode(
815 (str.charCodeAt(i) << 8) | str.charCodeAt(i + 1)));
816 }
817 } else {
818 for (i = 0; i < n; ++i) {
819 var code = PDFStringTranslateTable[str.charCodeAt(i)];
820 strBuf.push(code ? String.fromCharCode(code) : str.charAt(i));
821 }
822 }
823 return strBuf.join('');
824 }
826 function stringToUTF8String(str) {
827 return decodeURIComponent(escape(str));
828 }
830 function isEmptyObj(obj) {
831 for (var key in obj) {
832 return false;
833 }
834 return true;
835 }
837 function isBool(v) {
838 return typeof v == 'boolean';
839 }
841 function isInt(v) {
842 return typeof v == 'number' && ((v | 0) == v);
843 }
845 function isNum(v) {
846 return typeof v == 'number';
847 }
849 function isString(v) {
850 return typeof v == 'string';
851 }
853 function isNull(v) {
854 return v === null;
855 }
857 function isName(v) {
858 return v instanceof Name;
859 }
861 function isCmd(v, cmd) {
862 return v instanceof Cmd && (!cmd || v.cmd == cmd);
863 }
865 function isDict(v, type) {
866 if (!(v instanceof Dict)) {
867 return false;
868 }
869 if (!type) {
870 return true;
871 }
872 var dictType = v.get('Type');
873 return isName(dictType) && dictType.name == type;
874 }
876 function isArray(v) {
877 return v instanceof Array;
878 }
880 function isStream(v) {
881 return typeof v == 'object' && v !== null && v !== undefined &&
882 ('getBytes' in v);
883 }
885 function isArrayBuffer(v) {
886 return typeof v == 'object' && v !== null && v !== undefined &&
887 ('byteLength' in v);
888 }
890 function isRef(v) {
891 return v instanceof Ref;
892 }
894 function isPDFFunction(v) {
895 var fnDict;
896 if (typeof v != 'object') {
897 return false;
898 } else if (isDict(v)) {
899 fnDict = v;
900 } else if (isStream(v)) {
901 fnDict = v.dict;
902 } else {
903 return false;
904 }
905 return fnDict.has('FunctionType');
906 }
908 /**
909 * Legacy support for PDFJS Promise implementation.
910 * TODO remove eventually
911 * @ignore
912 */
913 var LegacyPromise = PDFJS.LegacyPromise = (function LegacyPromiseClosure() {
914 return function LegacyPromise() {
915 var resolve, reject;
916 var promise = new Promise(function (resolve_, reject_) {
917 resolve = resolve_;
918 reject = reject_;
919 });
920 promise.resolve = resolve;
921 promise.reject = reject;
922 return promise;
923 };
924 })();
926 /**
927 * Polyfill for Promises:
928 * The following promise implementation tries to generally implment the
929 * Promise/A+ spec. Some notable differences from other promise libaries are:
930 * - There currently isn't a seperate deferred and promise object.
931 * - Unhandled rejections eventually show an error if they aren't handled.
932 *
933 * Based off of the work in:
934 * https://bugzilla.mozilla.org/show_bug.cgi?id=810490
935 */
936 (function PromiseClosure() {
937 if (globalScope.Promise) {
938 // Promises existing in the DOM/Worker, checking presence of all/resolve
939 if (typeof globalScope.Promise.all !== 'function') {
940 globalScope.Promise.all = function (iterable) {
941 var count = 0, results = [], resolve, reject;
942 var promise = new globalScope.Promise(function (resolve_, reject_) {
943 resolve = resolve_;
944 reject = reject_;
945 });
946 iterable.forEach(function (p, i) {
947 count++;
948 p.then(function (result) {
949 results[i] = result;
950 count--;
951 if (count === 0) {
952 resolve(results);
953 }
954 }, reject);
955 });
956 if (count === 0) {
957 resolve(results);
958 }
959 return promise;
960 };
961 }
962 if (typeof globalScope.Promise.resolve !== 'function') {
963 globalScope.Promise.resolve = function (x) {
964 return new globalScope.Promise(function (resolve) { resolve(x); });
965 };
966 }
967 return;
968 }
969 throw new Error('DOM Promise is not present');
970 })();
972 var StatTimer = (function StatTimerClosure() {
973 function rpad(str, pad, length) {
974 while (str.length < length) {
975 str += pad;
976 }
977 return str;
978 }
979 function StatTimer() {
980 this.started = {};
981 this.times = [];
982 this.enabled = true;
983 }
984 StatTimer.prototype = {
985 time: function StatTimer_time(name) {
986 if (!this.enabled) {
987 return;
988 }
989 if (name in this.started) {
990 warn('Timer is already running for ' + name);
991 }
992 this.started[name] = Date.now();
993 },
994 timeEnd: function StatTimer_timeEnd(name) {
995 if (!this.enabled) {
996 return;
997 }
998 if (!(name in this.started)) {
999 warn('Timer has not been started for ' + name);
1000 }
1001 this.times.push({
1002 'name': name,
1003 'start': this.started[name],
1004 'end': Date.now()
1005 });
1006 // Remove timer from started so it can be called again.
1007 delete this.started[name];
1008 },
1009 toString: function StatTimer_toString() {
1010 var i, ii;
1011 var times = this.times;
1012 var out = '';
1013 // Find the longest name for padding purposes.
1014 var longest = 0;
1015 for (i = 0, ii = times.length; i < ii; ++i) {
1016 var name = times[i]['name'];
1017 if (name.length > longest) {
1018 longest = name.length;
1019 }
1020 }
1021 for (i = 0, ii = times.length; i < ii; ++i) {
1022 var span = times[i];
1023 var duration = span.end - span.start;
1024 out += rpad(span['name'], ' ', longest) + ' ' + duration + 'ms\n';
1025 }
1026 return out;
1027 }
1028 };
1029 return StatTimer;
1030 })();
1032 PDFJS.createBlob = function createBlob(data, contentType) {
1033 if (typeof Blob !== 'undefined') {
1034 return new Blob([data], { type: contentType });
1035 }
1036 // Blob builder is deprecated in FF14 and removed in FF18.
1037 var bb = new MozBlobBuilder();
1038 bb.append(data);
1039 return bb.getBlob(contentType);
1040 };
1042 PDFJS.createObjectURL = (function createObjectURLClosure() {
1043 // Blob/createObjectURL is not available, falling back to data schema.
1044 var digits =
1045 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
1047 return function createObjectURL(data, contentType) {
1048 if (!PDFJS.disableCreateObjectURL &&
1049 typeof URL !== 'undefined' && URL.createObjectURL) {
1050 var blob = PDFJS.createBlob(data, contentType);
1051 return URL.createObjectURL(blob);
1052 }
1054 var buffer = 'data:' + contentType + ';base64,';
1055 for (var i = 0, ii = data.length; i < ii; i += 3) {
1056 var b1 = data[i] & 0xFF;
1057 var b2 = data[i + 1] & 0xFF;
1058 var b3 = data[i + 2] & 0xFF;
1059 var d1 = b1 >> 2, d2 = ((b1 & 3) << 4) | (b2 >> 4);
1060 var d3 = i + 1 < ii ? ((b2 & 0xF) << 2) | (b3 >> 6) : 64;
1061 var d4 = i + 2 < ii ? (b3 & 0x3F) : 64;
1062 buffer += digits[d1] + digits[d2] + digits[d3] + digits[d4];
1063 }
1064 return buffer;
1065 };
1066 })();
1068 function MessageHandler(name, comObj) {
1069 this.name = name;
1070 this.comObj = comObj;
1071 this.callbackIndex = 1;
1072 this.postMessageTransfers = true;
1073 var callbacks = this.callbacks = {};
1074 var ah = this.actionHandler = {};
1076 ah['console_log'] = [function ahConsoleLog(data) {
1077 console.log.apply(console, data);
1078 }];
1079 ah['console_error'] = [function ahConsoleError(data) {
1080 console.error.apply(console, data);
1081 }];
1082 ah['_unsupported_feature'] = [function ah_unsupportedFeature(data) {
1083 UnsupportedManager.notify(data);
1084 }];
1086 comObj.onmessage = function messageHandlerComObjOnMessage(event) {
1087 var data = event.data;
1088 if (data.isReply) {
1089 var callbackId = data.callbackId;
1090 if (data.callbackId in callbacks) {
1091 var callback = callbacks[callbackId];
1092 delete callbacks[callbackId];
1093 callback(data.data);
1094 } else {
1095 error('Cannot resolve callback ' + callbackId);
1096 }
1097 } else if (data.action in ah) {
1098 var action = ah[data.action];
1099 if (data.callbackId) {
1100 var deferred = {};
1101 var promise = new Promise(function (resolve, reject) {
1102 deferred.resolve = resolve;
1103 deferred.reject = reject;
1104 });
1105 deferred.promise = promise;
1106 promise.then(function(resolvedData) {
1107 comObj.postMessage({
1108 isReply: true,
1109 callbackId: data.callbackId,
1110 data: resolvedData
1111 });
1112 });
1113 action[0].call(action[1], data.data, deferred);
1114 } else {
1115 action[0].call(action[1], data.data);
1116 }
1117 } else {
1118 error('Unkown action from worker: ' + data.action);
1119 }
1120 };
1121 }
1123 MessageHandler.prototype = {
1124 on: function messageHandlerOn(actionName, handler, scope) {
1125 var ah = this.actionHandler;
1126 if (ah[actionName]) {
1127 error('There is already an actionName called "' + actionName + '"');
1128 }
1129 ah[actionName] = [handler, scope];
1130 },
1131 /**
1132 * Sends a message to the comObj to invoke the action with the supplied data.
1133 * @param {String} actionName Action to call.
1134 * @param {JSON} data JSON data to send.
1135 * @param {function} [callback] Optional callback that will handle a reply.
1136 * @param {Array} [transfers] Optional list of transfers/ArrayBuffers
1137 */
1138 send: function messageHandlerSend(actionName, data, callback, transfers) {
1139 var message = {
1140 action: actionName,
1141 data: data
1142 };
1143 if (callback) {
1144 var callbackId = this.callbackIndex++;
1145 this.callbacks[callbackId] = callback;
1146 message.callbackId = callbackId;
1147 }
1148 if (transfers && this.postMessageTransfers) {
1149 this.comObj.postMessage(message, transfers);
1150 } else {
1151 this.comObj.postMessage(message);
1152 }
1153 }
1154 };
1156 function loadJpegStream(id, imageUrl, objs) {
1157 var img = new Image();
1158 img.onload = (function loadJpegStream_onloadClosure() {
1159 objs.resolve(id, img);
1160 });
1161 img.src = imageUrl;
1162 }
1165 var ColorSpace = (function ColorSpaceClosure() {
1166 // Constructor should define this.numComps, this.defaultColor, this.name
1167 function ColorSpace() {
1168 error('should not call ColorSpace constructor');
1169 }
1171 ColorSpace.prototype = {
1172 /**
1173 * Converts the color value to the RGB color. The color components are
1174 * located in the src array starting from the srcOffset. Returns the array
1175 * of the rgb components, each value ranging from [0,255].
1176 */
1177 getRgb: function ColorSpace_getRgb(src, srcOffset) {
1178 var rgb = new Uint8Array(3);
1179 this.getRgbItem(src, srcOffset, rgb, 0);
1180 return rgb;
1181 },
1182 /**
1183 * Converts the color value to the RGB color, similar to the getRgb method.
1184 * The result placed into the dest array starting from the destOffset.
1185 */
1186 getRgbItem: function ColorSpace_getRgbItem(src, srcOffset,
1187 dest, destOffset) {
1188 error('Should not call ColorSpace.getRgbItem');
1189 },
1190 /**
1191 * Converts the specified number of the color values to the RGB colors.
1192 * The colors are located in the src array starting from the srcOffset.
1193 * The result is placed into the dest array starting from the destOffset.
1194 * The src array items shall be in [0,2^bits) range, the dest array items
1195 * will be in [0,255] range. alpha01 indicates how many alpha components
1196 * there are in the dest array; it will be either 0 (RGB array) or 1 (RGBA
1197 * array).
1198 */
1199 getRgbBuffer: function ColorSpace_getRgbBuffer(src, srcOffset, count,
1200 dest, destOffset, bits,
1201 alpha01) {
1202 error('Should not call ColorSpace.getRgbBuffer');
1203 },
1204 /**
1205 * Determines the number of bytes required to store the result of the
1206 * conversion done by the getRgbBuffer method. As in getRgbBuffer,
1207 * |alpha01| is either 0 (RGB output) or 1 (RGBA output).
1208 */
1209 getOutputLength: function ColorSpace_getOutputLength(inputLength,
1210 alpha01) {
1211 error('Should not call ColorSpace.getOutputLength');
1212 },
1213 /**
1214 * Returns true if source data will be equal the result/output data.
1215 */
1216 isPassthrough: function ColorSpace_isPassthrough(bits) {
1217 return false;
1218 },
1219 /**
1220 * Fills in the RGB colors in the destination buffer. alpha01 indicates
1221 * how many alpha components there are in the dest array; it will be either
1222 * 0 (RGB array) or 1 (RGBA array).
1223 */
1224 fillRgb: function ColorSpace_fillRgb(dest, originalWidth,
1225 originalHeight, width, height,
1226 actualHeight, bpc, comps, alpha01) {
1227 var count = originalWidth * originalHeight;
1228 var rgbBuf = null;
1229 var numComponentColors = 1 << bpc;
1230 var needsResizing = originalHeight != height || originalWidth != width;
1231 var i, ii;
1233 if (this.isPassthrough(bpc)) {
1234 rgbBuf = comps;
1235 } else if (this.numComps === 1 && count > numComponentColors &&
1236 this.name !== 'DeviceGray' && this.name !== 'DeviceRGB') {
1237 // Optimization: create a color map when there is just one component and
1238 // we are converting more colors than the size of the color map. We
1239 // don't build the map if the colorspace is gray or rgb since those
1240 // methods are faster than building a map. This mainly offers big speed
1241 // ups for indexed and alternate colorspaces.
1242 //
1243 // TODO it may be worth while to cache the color map. While running
1244 // testing I never hit a cache so I will leave that out for now (perhaps
1245 // we are reparsing colorspaces too much?).
1246 var allColors = bpc <= 8 ? new Uint8Array(numComponentColors) :
1247 new Uint16Array(numComponentColors);
1248 var key;
1249 for (i = 0; i < numComponentColors; i++) {
1250 allColors[i] = i;
1251 }
1252 var colorMap = new Uint8Array(numComponentColors * 3);
1253 this.getRgbBuffer(allColors, 0, numComponentColors, colorMap, 0, bpc,
1254 /* alpha01 = */ 0);
1256 var destPos, rgbPos;
1257 if (!needsResizing) {
1258 // Fill in the RGB values directly into |dest|.
1259 destPos = 0;
1260 for (i = 0; i < count; ++i) {
1261 key = comps[i] * 3;
1262 dest[destPos++] = colorMap[key];
1263 dest[destPos++] = colorMap[key + 1];
1264 dest[destPos++] = colorMap[key + 2];
1265 destPos += alpha01;
1266 }
1267 } else {
1268 rgbBuf = new Uint8Array(count * 3);
1269 rgbPos = 0;
1270 for (i = 0; i < count; ++i) {
1271 key = comps[i] * 3;
1272 rgbBuf[rgbPos++] = colorMap[key];
1273 rgbBuf[rgbPos++] = colorMap[key + 1];
1274 rgbBuf[rgbPos++] = colorMap[key + 2];
1275 }
1276 }
1277 } else {
1278 if (!needsResizing) {
1279 // Fill in the RGB values directly into |dest|.
1280 this.getRgbBuffer(comps, 0, width * actualHeight, dest, 0, bpc,
1281 alpha01);
1282 } else {
1283 rgbBuf = new Uint8Array(count * 3);
1284 this.getRgbBuffer(comps, 0, count, rgbBuf, 0, bpc,
1285 /* alpha01 = */ 0);
1286 }
1287 }
1289 if (rgbBuf) {
1290 if (needsResizing) {
1291 rgbBuf = PDFImage.resize(rgbBuf, bpc, 3, originalWidth,
1292 originalHeight, width, height);
1293 }
1294 rgbPos = 0;
1295 destPos = 0;
1296 for (i = 0, ii = width * actualHeight; i < ii; i++) {
1297 dest[destPos++] = rgbBuf[rgbPos++];
1298 dest[destPos++] = rgbBuf[rgbPos++];
1299 dest[destPos++] = rgbBuf[rgbPos++];
1300 destPos += alpha01;
1301 }
1302 }
1303 },
1304 /**
1305 * True if the colorspace has components in the default range of [0, 1].
1306 * This should be true for all colorspaces except for lab color spaces
1307 * which are [0,100], [-128, 127], [-128, 127].
1308 */
1309 usesZeroToOneRange: true
1310 };
1312 ColorSpace.parse = function ColorSpace_parse(cs, xref, res) {
1313 var IR = ColorSpace.parseToIR(cs, xref, res);
1314 if (IR instanceof AlternateCS) {
1315 return IR;
1316 }
1317 return ColorSpace.fromIR(IR);
1318 };
1320 ColorSpace.fromIR = function ColorSpace_fromIR(IR) {
1321 var name = isArray(IR) ? IR[0] : IR;
1322 var whitePoint, blackPoint;
1324 switch (name) {
1325 case 'DeviceGrayCS':
1326 return this.singletons.gray;
1327 case 'DeviceRgbCS':
1328 return this.singletons.rgb;
1329 case 'DeviceCmykCS':
1330 return this.singletons.cmyk;
1331 case 'CalGrayCS':
1332 whitePoint = IR[1].WhitePoint;
1333 blackPoint = IR[1].BlackPoint;
1334 var gamma = IR[1].Gamma;
1335 return new CalGrayCS(whitePoint, blackPoint, gamma);
1336 case 'PatternCS':
1337 var basePatternCS = IR[1];
1338 if (basePatternCS) {
1339 basePatternCS = ColorSpace.fromIR(basePatternCS);
1340 }
1341 return new PatternCS(basePatternCS);
1342 case 'IndexedCS':
1343 var baseIndexedCS = IR[1];
1344 var hiVal = IR[2];
1345 var lookup = IR[3];
1346 return new IndexedCS(ColorSpace.fromIR(baseIndexedCS), hiVal, lookup);
1347 case 'AlternateCS':
1348 var numComps = IR[1];
1349 var alt = IR[2];
1350 var tintFnIR = IR[3];
1352 return new AlternateCS(numComps, ColorSpace.fromIR(alt),
1353 PDFFunction.fromIR(tintFnIR));
1354 case 'LabCS':
1355 whitePoint = IR[1].WhitePoint;
1356 blackPoint = IR[1].BlackPoint;
1357 var range = IR[1].Range;
1358 return new LabCS(whitePoint, blackPoint, range);
1359 default:
1360 error('Unkown name ' + name);
1361 }
1362 return null;
1363 };
1365 ColorSpace.parseToIR = function ColorSpace_parseToIR(cs, xref, res) {
1366 if (isName(cs)) {
1367 var colorSpaces = res.get('ColorSpace');
1368 if (isDict(colorSpaces)) {
1369 var refcs = colorSpaces.get(cs.name);
1370 if (refcs) {
1371 cs = refcs;
1372 }
1373 }
1374 }
1376 cs = xref.fetchIfRef(cs);
1377 var mode;
1379 if (isName(cs)) {
1380 mode = cs.name;
1381 this.mode = mode;
1383 switch (mode) {
1384 case 'DeviceGray':
1385 case 'G':
1386 return 'DeviceGrayCS';
1387 case 'DeviceRGB':
1388 case 'RGB':
1389 return 'DeviceRgbCS';
1390 case 'DeviceCMYK':
1391 case 'CMYK':
1392 return 'DeviceCmykCS';
1393 case 'Pattern':
1394 return ['PatternCS', null];
1395 default:
1396 error('unrecognized colorspace ' + mode);
1397 }
1398 } else if (isArray(cs)) {
1399 mode = cs[0].name;
1400 this.mode = mode;
1401 var numComps, params;
1403 switch (mode) {
1404 case 'DeviceGray':
1405 case 'G':
1406 return 'DeviceGrayCS';
1407 case 'DeviceRGB':
1408 case 'RGB':
1409 return 'DeviceRgbCS';
1410 case 'DeviceCMYK':
1411 case 'CMYK':
1412 return 'DeviceCmykCS';
1413 case 'CalGray':
1414 params = cs[1].getAll();
1415 return ['CalGrayCS', params];
1416 case 'CalRGB':
1417 return 'DeviceRgbCS';
1418 case 'ICCBased':
1419 var stream = xref.fetchIfRef(cs[1]);
1420 var dict = stream.dict;
1421 numComps = dict.get('N');
1422 if (numComps == 1) {
1423 return 'DeviceGrayCS';
1424 } else if (numComps == 3) {
1425 return 'DeviceRgbCS';
1426 } else if (numComps == 4) {
1427 return 'DeviceCmykCS';
1428 }
1429 break;
1430 case 'Pattern':
1431 var basePatternCS = cs[1];
1432 if (basePatternCS) {
1433 basePatternCS = ColorSpace.parseToIR(basePatternCS, xref, res);
1434 }
1435 return ['PatternCS', basePatternCS];
1436 case 'Indexed':
1437 case 'I':
1438 var baseIndexedCS = ColorSpace.parseToIR(cs[1], xref, res);
1439 var hiVal = cs[2] + 1;
1440 var lookup = xref.fetchIfRef(cs[3]);
1441 if (isStream(lookup)) {
1442 lookup = lookup.getBytes();
1443 }
1444 return ['IndexedCS', baseIndexedCS, hiVal, lookup];
1445 case 'Separation':
1446 case 'DeviceN':
1447 var name = cs[1];
1448 numComps = 1;
1449 if (isName(name)) {
1450 numComps = 1;
1451 } else if (isArray(name)) {
1452 numComps = name.length;
1453 }
1454 var alt = ColorSpace.parseToIR(cs[2], xref, res);
1455 var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3]));
1456 return ['AlternateCS', numComps, alt, tintFnIR];
1457 case 'Lab':
1458 params = cs[1].getAll();
1459 return ['LabCS', params];
1460 default:
1461 error('unimplemented color space object "' + mode + '"');
1462 }
1463 } else {
1464 error('unrecognized color space object: "' + cs + '"');
1465 }
1466 return null;
1467 };
1468 /**
1469 * Checks if a decode map matches the default decode map for a color space.
1470 * This handles the general decode maps where there are two values per
1471 * component. e.g. [0, 1, 0, 1, 0, 1] for a RGB color.
1472 * This does not handle Lab, Indexed, or Pattern decode maps since they are
1473 * slightly different.
1474 * @param {Array} decode Decode map (usually from an image).
1475 * @param {Number} n Number of components the color space has.
1476 */
1477 ColorSpace.isDefaultDecode = function ColorSpace_isDefaultDecode(decode, n) {
1478 if (!decode) {
1479 return true;
1480 }
1482 if (n * 2 !== decode.length) {
1483 warn('The decode map is not the correct length');
1484 return true;
1485 }
1486 for (var i = 0, ii = decode.length; i < ii; i += 2) {
1487 if (decode[i] !== 0 || decode[i + 1] != 1) {
1488 return false;
1489 }
1490 }
1491 return true;
1492 };
1494 ColorSpace.singletons = {
1495 get gray() {
1496 return shadow(this, 'gray', new DeviceGrayCS());
1497 },
1498 get rgb() {
1499 return shadow(this, 'rgb', new DeviceRgbCS());
1500 },
1501 get cmyk() {
1502 return shadow(this, 'cmyk', new DeviceCmykCS());
1503 }
1504 };
1506 return ColorSpace;
1507 })();
1509 /**
1510 * Alternate color space handles both Separation and DeviceN color spaces. A
1511 * Separation color space is actually just a DeviceN with one color component.
1512 * Both color spaces use a tinting function to convert colors to a base color
1513 * space.
1514 */
1515 var AlternateCS = (function AlternateCSClosure() {
1516 function AlternateCS(numComps, base, tintFn) {
1517 this.name = 'Alternate';
1518 this.numComps = numComps;
1519 this.defaultColor = new Float32Array(numComps);
1520 for (var i = 0; i < numComps; ++i) {
1521 this.defaultColor[i] = 1;
1522 }
1523 this.base = base;
1524 this.tintFn = tintFn;
1525 }
1527 AlternateCS.prototype = {
1528 getRgb: ColorSpace.prototype.getRgb,
1529 getRgbItem: function AlternateCS_getRgbItem(src, srcOffset,
1530 dest, destOffset) {
1531 var baseNumComps = this.base.numComps;
1532 var input = 'subarray' in src ?
1533 src.subarray(srcOffset, srcOffset + this.numComps) :
1534 Array.prototype.slice.call(src, srcOffset, srcOffset + this.numComps);
1535 var tinted = this.tintFn(input);
1536 this.base.getRgbItem(tinted, 0, dest, destOffset);
1537 },
1538 getRgbBuffer: function AlternateCS_getRgbBuffer(src, srcOffset, count,
1539 dest, destOffset, bits,
1540 alpha01) {
1541 var tintFn = this.tintFn;
1542 var base = this.base;
1543 var scale = 1 / ((1 << bits) - 1);
1544 var baseNumComps = base.numComps;
1545 var usesZeroToOneRange = base.usesZeroToOneRange;
1546 var isPassthrough = (base.isPassthrough(8) || !usesZeroToOneRange) &&
1547 alpha01 === 0;
1548 var pos = isPassthrough ? destOffset : 0;
1549 var baseBuf = isPassthrough ? dest : new Uint8Array(baseNumComps * count);
1550 var numComps = this.numComps;
1552 var scaled = new Float32Array(numComps);
1553 var i, j;
1554 for (i = 0; i < count; i++) {
1555 for (j = 0; j < numComps; j++) {
1556 scaled[j] = src[srcOffset++] * scale;
1557 }
1558 var tinted = tintFn(scaled);
1559 if (usesZeroToOneRange) {
1560 for (j = 0; j < baseNumComps; j++) {
1561 baseBuf[pos++] = tinted[j] * 255;
1562 }
1563 } else {
1564 base.getRgbItem(tinted, 0, baseBuf, pos);
1565 pos += baseNumComps;
1566 }
1567 }
1568 if (!isPassthrough) {
1569 base.getRgbBuffer(baseBuf, 0, count, dest, destOffset, 8, alpha01);
1570 }
1571 },
1572 getOutputLength: function AlternateCS_getOutputLength(inputLength,
1573 alpha01) {
1574 return this.base.getOutputLength(inputLength *
1575 this.base.numComps / this.numComps,
1576 alpha01);
1577 },
1578 isPassthrough: ColorSpace.prototype.isPassthrough,
1579 fillRgb: ColorSpace.prototype.fillRgb,
1580 isDefaultDecode: function AlternateCS_isDefaultDecode(decodeMap) {
1581 return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
1582 },
1583 usesZeroToOneRange: true
1584 };
1586 return AlternateCS;
1587 })();
1589 var PatternCS = (function PatternCSClosure() {
1590 function PatternCS(baseCS) {
1591 this.name = 'Pattern';
1592 this.base = baseCS;
1593 }
1594 PatternCS.prototype = {};
1596 return PatternCS;
1597 })();
1599 var IndexedCS = (function IndexedCSClosure() {
1600 function IndexedCS(base, highVal, lookup) {
1601 this.name = 'Indexed';
1602 this.numComps = 1;
1603 this.defaultColor = new Uint8Array([0]);
1604 this.base = base;
1605 this.highVal = highVal;
1607 var baseNumComps = base.numComps;
1608 var length = baseNumComps * highVal;
1609 var lookupArray;
1611 if (isStream(lookup)) {
1612 lookupArray = new Uint8Array(length);
1613 var bytes = lookup.getBytes(length);
1614 lookupArray.set(bytes);
1615 } else if (isString(lookup)) {
1616 lookupArray = new Uint8Array(length);
1617 for (var i = 0; i < length; ++i) {
1618 lookupArray[i] = lookup.charCodeAt(i);
1619 }
1620 } else if (lookup instanceof Uint8Array || lookup instanceof Array) {
1621 lookupArray = lookup;
1622 } else {
1623 error('Unrecognized lookup table: ' + lookup);
1624 }
1625 this.lookup = lookupArray;
1626 }
1628 IndexedCS.prototype = {
1629 getRgb: ColorSpace.prototype.getRgb,
1630 getRgbItem: function IndexedCS_getRgbItem(src, srcOffset,
1631 dest, destOffset) {
1632 var numComps = this.base.numComps;
1633 var start = src[srcOffset] * numComps;
1634 this.base.getRgbItem(this.lookup, start, dest, destOffset);
1635 },
1636 getRgbBuffer: function IndexedCS_getRgbBuffer(src, srcOffset, count,
1637 dest, destOffset, bits,
1638 alpha01) {
1639 var base = this.base;
1640 var numComps = base.numComps;
1641 var outputDelta = base.getOutputLength(numComps, alpha01);
1642 var lookup = this.lookup;
1644 for (var i = 0; i < count; ++i) {
1645 var lookupPos = src[srcOffset++] * numComps;
1646 base.getRgbBuffer(lookup, lookupPos, 1, dest, destOffset, 8, alpha01);
1647 destOffset += outputDelta;
1648 }
1649 },
1650 getOutputLength: function IndexedCS_getOutputLength(inputLength, alpha01) {
1651 return this.base.getOutputLength(inputLength * this.base.numComps,
1652 alpha01);
1653 },
1654 isPassthrough: ColorSpace.prototype.isPassthrough,
1655 fillRgb: ColorSpace.prototype.fillRgb,
1656 isDefaultDecode: function IndexedCS_isDefaultDecode(decodeMap) {
1657 // indexed color maps shouldn't be changed
1658 return true;
1659 },
1660 usesZeroToOneRange: true
1661 };
1662 return IndexedCS;
1663 })();
1665 var DeviceGrayCS = (function DeviceGrayCSClosure() {
1666 function DeviceGrayCS() {
1667 this.name = 'DeviceGray';
1668 this.numComps = 1;
1669 this.defaultColor = new Float32Array([0]);
1670 }
1672 DeviceGrayCS.prototype = {
1673 getRgb: ColorSpace.prototype.getRgb,
1674 getRgbItem: function DeviceGrayCS_getRgbItem(src, srcOffset,
1675 dest, destOffset) {
1676 var c = (src[srcOffset] * 255) | 0;
1677 c = c < 0 ? 0 : c > 255 ? 255 : c;
1678 dest[destOffset] = dest[destOffset + 1] = dest[destOffset + 2] = c;
1679 },
1680 getRgbBuffer: function DeviceGrayCS_getRgbBuffer(src, srcOffset, count,
1681 dest, destOffset, bits,
1682 alpha01) {
1683 var scale = 255 / ((1 << bits) - 1);
1684 var j = srcOffset, q = destOffset;
1685 for (var i = 0; i < count; ++i) {
1686 var c = (scale * src[j++]) | 0;
1687 dest[q++] = c;
1688 dest[q++] = c;
1689 dest[q++] = c;
1690 q += alpha01;
1691 }
1692 },
1693 getOutputLength: function DeviceGrayCS_getOutputLength(inputLength,
1694 alpha01) {
1695 return inputLength * (3 + alpha01);
1696 },
1697 isPassthrough: ColorSpace.prototype.isPassthrough,
1698 fillRgb: ColorSpace.prototype.fillRgb,
1699 isDefaultDecode: function DeviceGrayCS_isDefaultDecode(decodeMap) {
1700 return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
1701 },
1702 usesZeroToOneRange: true
1703 };
1704 return DeviceGrayCS;
1705 })();
1707 var DeviceRgbCS = (function DeviceRgbCSClosure() {
1708 function DeviceRgbCS() {
1709 this.name = 'DeviceRGB';
1710 this.numComps = 3;
1711 this.defaultColor = new Float32Array([0, 0, 0]);
1712 }
1713 DeviceRgbCS.prototype = {
1714 getRgb: ColorSpace.prototype.getRgb,
1715 getRgbItem: function DeviceRgbCS_getRgbItem(src, srcOffset,
1716 dest, destOffset) {
1717 var r = (src[srcOffset] * 255) | 0;
1718 var g = (src[srcOffset + 1] * 255) | 0;
1719 var b = (src[srcOffset + 2] * 255) | 0;
1720 dest[destOffset] = r < 0 ? 0 : r > 255 ? 255 : r;
1721 dest[destOffset + 1] = g < 0 ? 0 : g > 255 ? 255 : g;
1722 dest[destOffset + 2] = b < 0 ? 0 : b > 255 ? 255 : b;
1723 },
1724 getRgbBuffer: function DeviceRgbCS_getRgbBuffer(src, srcOffset, count,
1725 dest, destOffset, bits,
1726 alpha01) {
1727 if (bits === 8 && alpha01 === 0) {
1728 dest.set(src.subarray(srcOffset, srcOffset + count * 3), destOffset);
1729 return;
1730 }
1731 var scale = 255 / ((1 << bits) - 1);
1732 var j = srcOffset, q = destOffset;
1733 for (var i = 0; i < count; ++i) {
1734 dest[q++] = (scale * src[j++]) | 0;
1735 dest[q++] = (scale * src[j++]) | 0;
1736 dest[q++] = (scale * src[j++]) | 0;
1737 q += alpha01;
1738 }
1739 },
1740 getOutputLength: function DeviceRgbCS_getOutputLength(inputLength,
1741 alpha01) {
1742 return (inputLength * (3 + alpha01) / 3) | 0;
1743 },
1744 isPassthrough: function DeviceRgbCS_isPassthrough(bits) {
1745 return bits == 8;
1746 },
1747 fillRgb: ColorSpace.prototype.fillRgb,
1748 isDefaultDecode: function DeviceRgbCS_isDefaultDecode(decodeMap) {
1749 return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
1750 },
1751 usesZeroToOneRange: true
1752 };
1753 return DeviceRgbCS;
1754 })();
1756 var DeviceCmykCS = (function DeviceCmykCSClosure() {
1757 // The coefficients below was found using numerical analysis: the method of
1758 // steepest descent for the sum((f_i - color_value_i)^2) for r/g/b colors,
1759 // where color_value is the tabular value from the table of sampled RGB colors
1760 // from CMYK US Web Coated (SWOP) colorspace, and f_i is the corresponding
1761 // CMYK color conversion using the estimation below:
1762 // f(A, B,.. N) = Acc+Bcm+Ccy+Dck+c+Fmm+Gmy+Hmk+Im+Jyy+Kyk+Ly+Mkk+Nk+255
1763 function convertToRgb(src, srcOffset, srcScale, dest, destOffset) {
1764 var c = src[srcOffset + 0] * srcScale;
1765 var m = src[srcOffset + 1] * srcScale;
1766 var y = src[srcOffset + 2] * srcScale;
1767 var k = src[srcOffset + 3] * srcScale;
1769 var r =
1770 (c * (-4.387332384609988 * c + 54.48615194189176 * m +
1771 18.82290502165302 * y + 212.25662451639585 * k +
1772 -285.2331026137004) +
1773 m * (1.7149763477362134 * m - 5.6096736904047315 * y +
1774 -17.873870861415444 * k - 5.497006427196366) +
1775 y * (-2.5217340131683033 * y - 21.248923337353073 * k +
1776 17.5119270841813) +
1777 k * (-21.86122147463605 * k - 189.48180835922747) + 255) | 0;
1778 var g =
1779 (c * (8.841041422036149 * c + 60.118027045597366 * m +
1780 6.871425592049007 * y + 31.159100130055922 * k +
1781 -79.2970844816548) +
1782 m * (-15.310361306967817 * m + 17.575251261109482 * y +
1783 131.35250912493976 * k - 190.9453302588951) +
1784 y * (4.444339102852739 * y + 9.8632861493405 * k - 24.86741582555878) +
1785 k * (-20.737325471181034 * k - 187.80453709719578) + 255) | 0;
1786 var b =
1787 (c * (0.8842522430003296 * c + 8.078677503112928 * m +
1788 30.89978309703729 * y - 0.23883238689178934 * k +
1789 -14.183576799673286) +
1790 m * (10.49593273432072 * m + 63.02378494754052 * y +
1791 50.606957656360734 * k - 112.23884253719248) +
1792 y * (0.03296041114873217 * y + 115.60384449646641 * k +
1793 -193.58209356861505) +
1794 k * (-22.33816807309886 * k - 180.12613974708367) + 255) | 0;
1796 dest[destOffset] = r > 255 ? 255 : r < 0 ? 0 : r;
1797 dest[destOffset + 1] = g > 255 ? 255 : g < 0 ? 0 : g;
1798 dest[destOffset + 2] = b > 255 ? 255 : b < 0 ? 0 : b;
1799 }
1801 function DeviceCmykCS() {
1802 this.name = 'DeviceCMYK';
1803 this.numComps = 4;
1804 this.defaultColor = new Float32Array([0, 0, 0, 1]);
1805 }
1806 DeviceCmykCS.prototype = {
1807 getRgb: ColorSpace.prototype.getRgb,
1808 getRgbItem: function DeviceCmykCS_getRgbItem(src, srcOffset,
1809 dest, destOffset) {
1810 convertToRgb(src, srcOffset, 1, dest, destOffset);
1811 },
1812 getRgbBuffer: function DeviceCmykCS_getRgbBuffer(src, srcOffset, count,
1813 dest, destOffset, bits,
1814 alpha01) {
1815 var scale = 1 / ((1 << bits) - 1);
1816 for (var i = 0; i < count; i++) {
1817 convertToRgb(src, srcOffset, scale, dest, destOffset);
1818 srcOffset += 4;
1819 destOffset += 3 + alpha01;
1820 }
1821 },
1822 getOutputLength: function DeviceCmykCS_getOutputLength(inputLength,
1823 alpha01) {
1824 return (inputLength / 4 * (3 + alpha01)) | 0;
1825 },
1826 isPassthrough: ColorSpace.prototype.isPassthrough,
1827 fillRgb: ColorSpace.prototype.fillRgb,
1828 isDefaultDecode: function DeviceCmykCS_isDefaultDecode(decodeMap) {
1829 return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
1830 },
1831 usesZeroToOneRange: true
1832 };
1834 return DeviceCmykCS;
1835 })();
1837 //
1838 // CalGrayCS: Based on "PDF Reference, Sixth Ed", p.245
1839 //
1840 var CalGrayCS = (function CalGrayCSClosure() {
1841 function CalGrayCS(whitePoint, blackPoint, gamma) {
1842 this.name = 'CalGray';
1843 this.numComps = 1;
1844 this.defaultColor = new Float32Array([0]);
1846 if (!whitePoint) {
1847 error('WhitePoint missing - required for color space CalGray');
1848 }
1849 blackPoint = blackPoint || [0, 0, 0];
1850 gamma = gamma || 1;
1852 // Translate arguments to spec variables.
1853 this.XW = whitePoint[0];
1854 this.YW = whitePoint[1];
1855 this.ZW = whitePoint[2];
1857 this.XB = blackPoint[0];
1858 this.YB = blackPoint[1];
1859 this.ZB = blackPoint[2];
1861 this.G = gamma;
1863 // Validate variables as per spec.
1864 if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) {
1865 error('Invalid WhitePoint components for ' + this.name +
1866 ', no fallback available');
1867 }
1869 if (this.XB < 0 || this.YB < 0 || this.ZB < 0) {
1870 info('Invalid BlackPoint for ' + this.name + ', falling back to default');
1871 this.XB = this.YB = this.ZB = 0;
1872 }
1874 if (this.XB !== 0 || this.YB !== 0 || this.ZB !== 0) {
1875 warn(this.name + ', BlackPoint: XB: ' + this.XB + ', YB: ' + this.YB +
1876 ', ZB: ' + this.ZB + ', only default values are supported.');
1877 }
1879 if (this.G < 1) {
1880 info('Invalid Gamma: ' + this.G + ' for ' + this.name +
1881 ', falling back to default');
1882 this.G = 1;
1883 }
1884 }
1886 function convertToRgb(cs, src, srcOffset, dest, destOffset, scale) {
1887 // A represents a gray component of a calibrated gray space.
1888 // A <---> AG in the spec
1889 var A = src[srcOffset] * scale;
1890 var AG = Math.pow(A, cs.G);
1892 // Computes intermediate variables M, L, N as per spec.
1893 // Except if other than default BlackPoint values are used.
1894 var M = cs.XW * AG;
1895 var L = cs.YW * AG;
1896 var N = cs.ZW * AG;
1898 // Decode XYZ, as per spec.
1899 var X = M;
1900 var Y = L;
1901 var Z = N;
1903 // http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html, Ch 4.
1904 // This yields values in range [0, 100].
1905 var Lstar = Math.max(116 * Math.pow(Y, 1 / 3) - 16, 0);
1907 // Convert values to rgb range [0, 255].
1908 dest[destOffset] = Lstar * 255 / 100;
1909 dest[destOffset + 1] = Lstar * 255 / 100;
1910 dest[destOffset + 2] = Lstar * 255 / 100;
1911 }
1913 CalGrayCS.prototype = {
1914 getRgb: ColorSpace.prototype.getRgb,
1915 getRgbItem: function CalGrayCS_getRgbItem(src, srcOffset,
1916 dest, destOffset) {
1917 convertToRgb(this, src, srcOffset, dest, destOffset, 1);
1918 },
1919 getRgbBuffer: function CalGrayCS_getRgbBuffer(src, srcOffset, count,
1920 dest, destOffset, bits,
1921 alpha01) {
1922 var scale = 1 / ((1 << bits) - 1);
1924 for (var i = 0; i < count; ++i) {
1925 convertToRgb(this, src, srcOffset, dest, destOffset, scale);
1926 srcOffset += 1;
1927 destOffset += 3 + alpha01;
1928 }
1929 },
1930 getOutputLength: function CalGrayCS_getOutputLength(inputLength, alpha01) {
1931 return inputLength * (3 + alpha01);
1932 },
1933 isPassthrough: ColorSpace.prototype.isPassthrough,
1934 fillRgb: ColorSpace.prototype.fillRgb,
1935 isDefaultDecode: function CalGrayCS_isDefaultDecode(decodeMap) {
1936 return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
1937 },
1938 usesZeroToOneRange: true
1939 };
1940 return CalGrayCS;
1941 })();
1943 //
1944 // LabCS: Based on "PDF Reference, Sixth Ed", p.250
1945 //
1946 var LabCS = (function LabCSClosure() {
1947 function LabCS(whitePoint, blackPoint, range) {
1948 this.name = 'Lab';
1949 this.numComps = 3;
1950 this.defaultColor = new Float32Array([0, 0, 0]);
1952 if (!whitePoint) {
1953 error('WhitePoint missing - required for color space Lab');
1954 }
1955 blackPoint = blackPoint || [0, 0, 0];
1956 range = range || [-100, 100, -100, 100];
1958 // Translate args to spec variables
1959 this.XW = whitePoint[0];
1960 this.YW = whitePoint[1];
1961 this.ZW = whitePoint[2];
1962 this.amin = range[0];
1963 this.amax = range[1];
1964 this.bmin = range[2];
1965 this.bmax = range[3];
1967 // These are here just for completeness - the spec doesn't offer any
1968 // formulas that use BlackPoint in Lab
1969 this.XB = blackPoint[0];
1970 this.YB = blackPoint[1];
1971 this.ZB = blackPoint[2];
1973 // Validate vars as per spec
1974 if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) {
1975 error('Invalid WhitePoint components, no fallback available');
1976 }
1978 if (this.XB < 0 || this.YB < 0 || this.ZB < 0) {
1979 info('Invalid BlackPoint, falling back to default');
1980 this.XB = this.YB = this.ZB = 0;
1981 }
1983 if (this.amin > this.amax || this.bmin > this.bmax) {
1984 info('Invalid Range, falling back to defaults');
1985 this.amin = -100;
1986 this.amax = 100;
1987 this.bmin = -100;
1988 this.bmax = 100;
1989 }
1990 }
1992 // Function g(x) from spec
1993 function fn_g(x) {
1994 if (x >= 6 / 29) {
1995 return x * x * x;
1996 } else {
1997 return (108 / 841) * (x - 4 / 29);
1998 }
1999 }
2001 function decode(value, high1, low2, high2) {
2002 return low2 + (value) * (high2 - low2) / (high1);
2003 }
2005 // If decoding is needed maxVal should be 2^bits per component - 1.
2006 function convertToRgb(cs, src, srcOffset, maxVal, dest, destOffset) {
2007 // XXX: Lab input is in the range of [0, 100], [amin, amax], [bmin, bmax]
2008 // not the usual [0, 1]. If a command like setFillColor is used the src
2009 // values will already be within the correct range. However, if we are
2010 // converting an image we have to map the values to the correct range given
2011 // above.
2012 // Ls,as,bs <---> L*,a*,b* in the spec
2013 var Ls = src[srcOffset];
2014 var as = src[srcOffset + 1];
2015 var bs = src[srcOffset + 2];
2016 if (maxVal !== false) {
2017 Ls = decode(Ls, maxVal, 0, 100);
2018 as = decode(as, maxVal, cs.amin, cs.amax);
2019 bs = decode(bs, maxVal, cs.bmin, cs.bmax);
2020 }
2022 // Adjust limits of 'as' and 'bs'
2023 as = as > cs.amax ? cs.amax : as < cs.amin ? cs.amin : as;
2024 bs = bs > cs.bmax ? cs.bmax : bs < cs.bmin ? cs.bmin : bs;
2026 // Computes intermediate variables X,Y,Z as per spec
2027 var M = (Ls + 16) / 116;
2028 var L = M + (as / 500);
2029 var N = M - (bs / 200);
2031 var X = cs.XW * fn_g(L);
2032 var Y = cs.YW * fn_g(M);
2033 var Z = cs.ZW * fn_g(N);
2035 var r, g, b;
2036 // Using different conversions for D50 and D65 white points,
2037 // per http://www.color.org/srgb.pdf
2038 if (cs.ZW < 1) {
2039 // Assuming D50 (X=0.9642, Y=1.00, Z=0.8249)
2040 r = X * 3.1339 + Y * -1.6170 + Z * -0.4906;
2041 g = X * -0.9785 + Y * 1.9160 + Z * 0.0333;
2042 b = X * 0.0720 + Y * -0.2290 + Z * 1.4057;
2043 } else {
2044 // Assuming D65 (X=0.9505, Y=1.00, Z=1.0888)
2045 r = X * 3.2406 + Y * -1.5372 + Z * -0.4986;
2046 g = X * -0.9689 + Y * 1.8758 + Z * 0.0415;
2047 b = X * 0.0557 + Y * -0.2040 + Z * 1.0570;
2048 }
2049 // clamp color values to [0,1] range then convert to [0,255] range.
2050 dest[destOffset] = r <= 0 ? 0 : r >= 1 ? 255 : Math.sqrt(r) * 255 | 0;
2051 dest[destOffset + 1] = g <= 0 ? 0 : g >= 1 ? 255 : Math.sqrt(g) * 255 | 0;
2052 dest[destOffset + 2] = b <= 0 ? 0 : b >= 1 ? 255 : Math.sqrt(b) * 255 | 0;
2053 }
2055 LabCS.prototype = {
2056 getRgb: ColorSpace.prototype.getRgb,
2057 getRgbItem: function LabCS_getRgbItem(src, srcOffset, dest, destOffset) {
2058 convertToRgb(this, src, srcOffset, false, dest, destOffset);
2059 },
2060 getRgbBuffer: function LabCS_getRgbBuffer(src, srcOffset, count,
2061 dest, destOffset, bits,
2062 alpha01) {
2063 var maxVal = (1 << bits) - 1;
2064 for (var i = 0; i < count; i++) {
2065 convertToRgb(this, src, srcOffset, maxVal, dest, destOffset);
2066 srcOffset += 3;
2067 destOffset += 3 + alpha01;
2068 }
2069 },
2070 getOutputLength: function LabCS_getOutputLength(inputLength, alpha01) {
2071 return (inputLength * (3 + alpha01) / 3) | 0;
2072 },
2073 isPassthrough: ColorSpace.prototype.isPassthrough,
2074 isDefaultDecode: function LabCS_isDefaultDecode(decodeMap) {
2075 // XXX: Decoding is handled with the lab conversion because of the strange
2076 // ranges that are used.
2077 return true;
2078 },
2079 usesZeroToOneRange: false
2080 };
2081 return LabCS;
2082 })();
2086 var PDFFunction = (function PDFFunctionClosure() {
2087 var CONSTRUCT_SAMPLED = 0;
2088 var CONSTRUCT_INTERPOLATED = 2;
2089 var CONSTRUCT_STICHED = 3;
2090 var CONSTRUCT_POSTSCRIPT = 4;
2092 return {
2093 getSampleArray: function PDFFunction_getSampleArray(size, outputSize, bps,
2094 str) {
2095 var i, ii;
2096 var length = 1;
2097 for (i = 0, ii = size.length; i < ii; i++) {
2098 length *= size[i];
2099 }
2100 length *= outputSize;
2102 var array = [];
2103 var codeSize = 0;
2104 var codeBuf = 0;
2105 // 32 is a valid bps so shifting won't work
2106 var sampleMul = 1.0 / (Math.pow(2.0, bps) - 1);
2108 var strBytes = str.getBytes((length * bps + 7) / 8);
2109 var strIdx = 0;
2110 for (i = 0; i < length; i++) {
2111 while (codeSize < bps) {
2112 codeBuf <<= 8;
2113 codeBuf |= strBytes[strIdx++];
2114 codeSize += 8;
2115 }
2116 codeSize -= bps;
2117 array.push((codeBuf >> codeSize) * sampleMul);
2118 codeBuf &= (1 << codeSize) - 1;
2119 }
2120 return array;
2121 },
2123 getIR: function PDFFunction_getIR(xref, fn) {
2124 var dict = fn.dict;
2125 if (!dict) {
2126 dict = fn;
2127 }
2129 var types = [this.constructSampled,
2130 null,
2131 this.constructInterpolated,
2132 this.constructStiched,
2133 this.constructPostScript];
2135 var typeNum = dict.get('FunctionType');
2136 var typeFn = types[typeNum];
2137 if (!typeFn) {
2138 error('Unknown type of function');
2139 }
2141 return typeFn.call(this, fn, dict, xref);
2142 },
2144 fromIR: function PDFFunction_fromIR(IR) {
2145 var type = IR[0];
2146 switch (type) {
2147 case CONSTRUCT_SAMPLED:
2148 return this.constructSampledFromIR(IR);
2149 case CONSTRUCT_INTERPOLATED:
2150 return this.constructInterpolatedFromIR(IR);
2151 case CONSTRUCT_STICHED:
2152 return this.constructStichedFromIR(IR);
2153 //case CONSTRUCT_POSTSCRIPT:
2154 default:
2155 return this.constructPostScriptFromIR(IR);
2156 }
2157 },
2159 parse: function PDFFunction_parse(xref, fn) {
2160 var IR = this.getIR(xref, fn);
2161 return this.fromIR(IR);
2162 },
2164 constructSampled: function PDFFunction_constructSampled(str, dict) {
2165 function toMultiArray(arr) {
2166 var inputLength = arr.length;
2167 var out = [];
2168 var index = 0;
2169 for (var i = 0; i < inputLength; i += 2) {
2170 out[index] = [arr[i], arr[i + 1]];
2171 ++index;
2172 }
2173 return out;
2174 }
2175 var domain = dict.get('Domain');
2176 var range = dict.get('Range');
2178 if (!domain || !range) {
2179 error('No domain or range');
2180 }
2182 var inputSize = domain.length / 2;
2183 var outputSize = range.length / 2;
2185 domain = toMultiArray(domain);
2186 range = toMultiArray(range);
2188 var size = dict.get('Size');
2189 var bps = dict.get('BitsPerSample');
2190 var order = dict.get('Order') || 1;
2191 if (order !== 1) {
2192 // No description how cubic spline interpolation works in PDF32000:2008
2193 // As in poppler, ignoring order, linear interpolation may work as good
2194 info('No support for cubic spline interpolation: ' + order);
2195 }
2197 var encode = dict.get('Encode');
2198 if (!encode) {
2199 encode = [];
2200 for (var i = 0; i < inputSize; ++i) {
2201 encode.push(0);
2202 encode.push(size[i] - 1);
2203 }
2204 }
2205 encode = toMultiArray(encode);
2207 var decode = dict.get('Decode');
2208 if (!decode) {
2209 decode = range;
2210 } else {
2211 decode = toMultiArray(decode);
2212 }
2214 var samples = this.getSampleArray(size, outputSize, bps, str);
2216 return [
2217 CONSTRUCT_SAMPLED, inputSize, domain, encode, decode, samples, size,
2218 outputSize, Math.pow(2, bps) - 1, range
2219 ];
2220 },
2222 constructSampledFromIR: function PDFFunction_constructSampledFromIR(IR) {
2223 // See chapter 3, page 109 of the PDF reference
2224 function interpolate(x, xmin, xmax, ymin, ymax) {
2225 return ymin + ((x - xmin) * ((ymax - ymin) / (xmax - xmin)));
2226 }
2228 return function constructSampledFromIRResult(args) {
2229 // See chapter 3, page 110 of the PDF reference.
2230 var m = IR[1];
2231 var domain = IR[2];
2232 var encode = IR[3];
2233 var decode = IR[4];
2234 var samples = IR[5];
2235 var size = IR[6];
2236 var n = IR[7];
2237 //var mask = IR[8];
2238 var range = IR[9];
2240 if (m != args.length) {
2241 error('Incorrect number of arguments: ' + m + ' != ' +
2242 args.length);
2243 }
2245 var x = args;
2247 // Building the cube vertices: its part and sample index
2248 // http://rjwagner49.com/Mathematics/Interpolation.pdf
2249 var cubeVertices = 1 << m;
2250 var cubeN = new Float64Array(cubeVertices);
2251 var cubeVertex = new Uint32Array(cubeVertices);
2252 var i, j;
2253 for (j = 0; j < cubeVertices; j++) {
2254 cubeN[j] = 1;
2255 }
2257 var k = n, pos = 1;
2258 // Map x_i to y_j for 0 <= i < m using the sampled function.
2259 for (i = 0; i < m; ++i) {
2260 // x_i' = min(max(x_i, Domain_2i), Domain_2i+1)
2261 var domain_2i = domain[i][0];
2262 var domain_2i_1 = domain[i][1];
2263 var xi = Math.min(Math.max(x[i], domain_2i), domain_2i_1);
2265 // e_i = Interpolate(x_i', Domain_2i, Domain_2i+1,
2266 // Encode_2i, Encode_2i+1)
2267 var e = interpolate(xi, domain_2i, domain_2i_1,
2268 encode[i][0], encode[i][1]);
2270 // e_i' = min(max(e_i, 0), Size_i - 1)
2271 var size_i = size[i];
2272 e = Math.min(Math.max(e, 0), size_i - 1);
2274 // Adjusting the cube: N and vertex sample index
2275 var e0 = e < size_i - 1 ? Math.floor(e) : e - 1; // e1 = e0 + 1;
2276 var n0 = e0 + 1 - e; // (e1 - e) / (e1 - e0);
2277 var n1 = e - e0; // (e - e0) / (e1 - e0);
2278 var offset0 = e0 * k;
2279 var offset1 = offset0 + k; // e1 * k
2280 for (j = 0; j < cubeVertices; j++) {
2281 if (j & pos) {
2282 cubeN[j] *= n1;
2283 cubeVertex[j] += offset1;
2284 } else {
2285 cubeN[j] *= n0;
2286 cubeVertex[j] += offset0;
2287 }
2288 }
2290 k *= size_i;
2291 pos <<= 1;
2292 }
2294 var y = new Float64Array(n);
2295 for (j = 0; j < n; ++j) {
2296 // Sum all cube vertices' samples portions
2297 var rj = 0;
2298 for (i = 0; i < cubeVertices; i++) {
2299 rj += samples[cubeVertex[i] + j] * cubeN[i];
2300 }
2302 // r_j' = Interpolate(r_j, 0, 2^BitsPerSample - 1,
2303 // Decode_2j, Decode_2j+1)
2304 rj = interpolate(rj, 0, 1, decode[j][0], decode[j][1]);
2306 // y_j = min(max(r_j, range_2j), range_2j+1)
2307 y[j] = Math.min(Math.max(rj, range[j][0]), range[j][1]);
2308 }
2310 return y;
2311 };
2312 },
2314 constructInterpolated: function PDFFunction_constructInterpolated(str,
2315 dict) {
2316 var c0 = dict.get('C0') || [0];
2317 var c1 = dict.get('C1') || [1];
2318 var n = dict.get('N');
2320 if (!isArray(c0) || !isArray(c1)) {
2321 error('Illegal dictionary for interpolated function');
2322 }
2324 var length = c0.length;
2325 var diff = [];
2326 for (var i = 0; i < length; ++i) {
2327 diff.push(c1[i] - c0[i]);
2328 }
2330 return [CONSTRUCT_INTERPOLATED, c0, diff, n];
2331 },
2333 constructInterpolatedFromIR:
2334 function PDFFunction_constructInterpolatedFromIR(IR) {
2335 var c0 = IR[1];
2336 var diff = IR[2];
2337 var n = IR[3];
2339 var length = diff.length;
2341 return function constructInterpolatedFromIRResult(args) {
2342 var x = n == 1 ? args[0] : Math.pow(args[0], n);
2344 var out = [];
2345 for (var j = 0; j < length; ++j) {
2346 out.push(c0[j] + (x * diff[j]));
2347 }
2349 return out;
2351 };
2352 },
2354 constructStiched: function PDFFunction_constructStiched(fn, dict, xref) {
2355 var domain = dict.get('Domain');
2357 if (!domain) {
2358 error('No domain');
2359 }
2361 var inputSize = domain.length / 2;
2362 if (inputSize != 1) {
2363 error('Bad domain for stiched function');
2364 }
2366 var fnRefs = dict.get('Functions');
2367 var fns = [];
2368 for (var i = 0, ii = fnRefs.length; i < ii; ++i) {
2369 fns.push(PDFFunction.getIR(xref, xref.fetchIfRef(fnRefs[i])));
2370 }
2372 var bounds = dict.get('Bounds');
2373 var encode = dict.get('Encode');
2375 return [CONSTRUCT_STICHED, domain, bounds, encode, fns];
2376 },
2378 constructStichedFromIR: function PDFFunction_constructStichedFromIR(IR) {
2379 var domain = IR[1];
2380 var bounds = IR[2];
2381 var encode = IR[3];
2382 var fnsIR = IR[4];
2383 var fns = [];
2385 for (var i = 0, ii = fnsIR.length; i < ii; i++) {
2386 fns.push(PDFFunction.fromIR(fnsIR[i]));
2387 }
2389 return function constructStichedFromIRResult(args) {
2390 var clip = function constructStichedFromIRClip(v, min, max) {
2391 if (v > max) {
2392 v = max;
2393 } else if (v < min) {
2394 v = min;
2395 }
2396 return v;
2397 };
2399 // clip to domain
2400 var v = clip(args[0], domain[0], domain[1]);
2401 // calulate which bound the value is in
2402 for (var i = 0, ii = bounds.length; i < ii; ++i) {
2403 if (v < bounds[i]) {
2404 break;
2405 }
2406 }
2408 // encode value into domain of function
2409 var dmin = domain[0];
2410 if (i > 0) {
2411 dmin = bounds[i - 1];
2412 }
2413 var dmax = domain[1];
2414 if (i < bounds.length) {
2415 dmax = bounds[i];
2416 }
2418 var rmin = encode[2 * i];
2419 var rmax = encode[2 * i + 1];
2421 var v2 = rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin);
2423 // call the appropriate function
2424 return fns[i]([v2]);
2425 };
2426 },
2428 constructPostScript: function PDFFunction_constructPostScript(fn, dict,
2429 xref) {
2430 var domain = dict.get('Domain');
2431 var range = dict.get('Range');
2433 if (!domain) {
2434 error('No domain.');
2435 }
2437 if (!range) {
2438 error('No range.');
2439 }
2441 var lexer = new PostScriptLexer(fn);
2442 var parser = new PostScriptParser(lexer);
2443 var code = parser.parse();
2445 return [CONSTRUCT_POSTSCRIPT, domain, range, code];
2446 },
2448 constructPostScriptFromIR: function PDFFunction_constructPostScriptFromIR(
2449 IR) {
2450 var domain = IR[1];
2451 var range = IR[2];
2452 var code = IR[3];
2453 var numOutputs = range.length / 2;
2454 var evaluator = new PostScriptEvaluator(code);
2455 // Cache the values for a big speed up, the cache size is limited though
2456 // since the number of possible values can be huge from a PS function.
2457 var cache = new FunctionCache();
2458 return function constructPostScriptFromIRResult(args) {
2459 var initialStack = [];
2460 for (var i = 0, ii = (domain.length / 2); i < ii; ++i) {
2461 initialStack.push(args[i]);
2462 }
2464 var key = initialStack.join('_');
2465 if (cache.has(key)) {
2466 return cache.get(key);
2467 }
2469 var stack = evaluator.execute(initialStack);
2470 var transformed = [];
2471 for (i = numOutputs - 1; i >= 0; --i) {
2472 var out = stack.pop();
2473 var rangeIndex = 2 * i;
2474 if (out < range[rangeIndex]) {
2475 out = range[rangeIndex];
2476 } else if (out > range[rangeIndex + 1]) {
2477 out = range[rangeIndex + 1];
2478 }
2479 transformed[i] = out;
2480 }
2481 cache.set(key, transformed);
2482 return transformed;
2483 };
2484 }
2485 };
2486 })();
2488 var FunctionCache = (function FunctionCacheClosure() {
2489 // Of 10 PDF's with type4 functions the maxium number of distinct values seen
2490 // was 256. This still may need some tweaking in the future though.
2491 var MAX_CACHE_SIZE = 1024;
2492 function FunctionCache() {
2493 this.cache = {};
2494 this.total = 0;
2495 }
2496 FunctionCache.prototype = {
2497 has: function FunctionCache_has(key) {
2498 return key in this.cache;
2499 },
2500 get: function FunctionCache_get(key) {
2501 return this.cache[key];
2502 },
2503 set: function FunctionCache_set(key, value) {
2504 if (this.total < MAX_CACHE_SIZE) {
2505 this.cache[key] = value;
2506 this.total++;
2507 }
2508 }
2509 };
2510 return FunctionCache;
2511 })();
2513 var PostScriptStack = (function PostScriptStackClosure() {
2514 var MAX_STACK_SIZE = 100;
2515 function PostScriptStack(initialStack) {
2516 this.stack = initialStack || [];
2517 }
2519 PostScriptStack.prototype = {
2520 push: function PostScriptStack_push(value) {
2521 if (this.stack.length >= MAX_STACK_SIZE) {
2522 error('PostScript function stack overflow.');
2523 }
2524 this.stack.push(value);
2525 },
2526 pop: function PostScriptStack_pop() {
2527 if (this.stack.length <= 0) {
2528 error('PostScript function stack underflow.');
2529 }
2530 return this.stack.pop();
2531 },
2532 copy: function PostScriptStack_copy(n) {
2533 if (this.stack.length + n >= MAX_STACK_SIZE) {
2534 error('PostScript function stack overflow.');
2535 }
2536 var stack = this.stack;
2537 for (var i = stack.length - n, j = n - 1; j >= 0; j--, i++) {
2538 stack.push(stack[i]);
2539 }
2540 },
2541 index: function PostScriptStack_index(n) {
2542 this.push(this.stack[this.stack.length - n - 1]);
2543 },
2544 // rotate the last n stack elements p times
2545 roll: function PostScriptStack_roll(n, p) {
2546 var stack = this.stack;
2547 var l = stack.length - n;
2548 var r = stack.length - 1, c = l + (p - Math.floor(p / n) * n), i, j, t;
2549 for (i = l, j = r; i < j; i++, j--) {
2550 t = stack[i]; stack[i] = stack[j]; stack[j] = t;
2551 }
2552 for (i = l, j = c - 1; i < j; i++, j--) {
2553 t = stack[i]; stack[i] = stack[j]; stack[j] = t;
2554 }
2555 for (i = c, j = r; i < j; i++, j--) {
2556 t = stack[i]; stack[i] = stack[j]; stack[j] = t;
2557 }
2558 }
2559 };
2560 return PostScriptStack;
2561 })();
2562 var PostScriptEvaluator = (function PostScriptEvaluatorClosure() {
2563 function PostScriptEvaluator(operators) {
2564 this.operators = operators;
2565 }
2566 PostScriptEvaluator.prototype = {
2567 execute: function PostScriptEvaluator_execute(initialStack) {
2568 var stack = new PostScriptStack(initialStack);
2569 var counter = 0;
2570 var operators = this.operators;
2571 var length = operators.length;
2572 var operator, a, b;
2573 while (counter < length) {
2574 operator = operators[counter++];
2575 if (typeof operator == 'number') {
2576 // Operator is really an operand and should be pushed to the stack.
2577 stack.push(operator);
2578 continue;
2579 }
2580 switch (operator) {
2581 // non standard ps operators
2582 case 'jz': // jump if false
2583 b = stack.pop();
2584 a = stack.pop();
2585 if (!a) {
2586 counter = b;
2587 }
2588 break;
2589 case 'j': // jump
2590 a = stack.pop();
2591 counter = a;
2592 break;
2594 // all ps operators in alphabetical order (excluding if/ifelse)
2595 case 'abs':
2596 a = stack.pop();
2597 stack.push(Math.abs(a));
2598 break;
2599 case 'add':
2600 b = stack.pop();
2601 a = stack.pop();
2602 stack.push(a + b);
2603 break;
2604 case 'and':
2605 b = stack.pop();
2606 a = stack.pop();
2607 if (isBool(a) && isBool(b)) {
2608 stack.push(a && b);
2609 } else {
2610 stack.push(a & b);
2611 }
2612 break;
2613 case 'atan':
2614 a = stack.pop();
2615 stack.push(Math.atan(a));
2616 break;
2617 case 'bitshift':
2618 b = stack.pop();
2619 a = stack.pop();
2620 if (a > 0) {
2621 stack.push(a << b);
2622 } else {
2623 stack.push(a >> b);
2624 }
2625 break;
2626 case 'ceiling':
2627 a = stack.pop();
2628 stack.push(Math.ceil(a));
2629 break;
2630 case 'copy':
2631 a = stack.pop();
2632 stack.copy(a);
2633 break;
2634 case 'cos':
2635 a = stack.pop();
2636 stack.push(Math.cos(a));
2637 break;
2638 case 'cvi':
2639 a = stack.pop() | 0;
2640 stack.push(a);
2641 break;
2642 case 'cvr':
2643 // noop
2644 break;
2645 case 'div':
2646 b = stack.pop();
2647 a = stack.pop();
2648 stack.push(a / b);
2649 break;
2650 case 'dup':
2651 stack.copy(1);
2652 break;
2653 case 'eq':
2654 b = stack.pop();
2655 a = stack.pop();
2656 stack.push(a == b);
2657 break;
2658 case 'exch':
2659 stack.roll(2, 1);
2660 break;
2661 case 'exp':
2662 b = stack.pop();
2663 a = stack.pop();
2664 stack.push(Math.pow(a, b));
2665 break;
2666 case 'false':
2667 stack.push(false);
2668 break;
2669 case 'floor':
2670 a = stack.pop();
2671 stack.push(Math.floor(a));
2672 break;
2673 case 'ge':
2674 b = stack.pop();
2675 a = stack.pop();
2676 stack.push(a >= b);
2677 break;
2678 case 'gt':
2679 b = stack.pop();
2680 a = stack.pop();
2681 stack.push(a > b);
2682 break;
2683 case 'idiv':
2684 b = stack.pop();
2685 a = stack.pop();
2686 stack.push((a / b) | 0);
2687 break;
2688 case 'index':
2689 a = stack.pop();
2690 stack.index(a);
2691 break;
2692 case 'le':
2693 b = stack.pop();
2694 a = stack.pop();
2695 stack.push(a <= b);
2696 break;
2697 case 'ln':
2698 a = stack.pop();
2699 stack.push(Math.log(a));
2700 break;
2701 case 'log':
2702 a = stack.pop();
2703 stack.push(Math.log(a) / Math.LN10);
2704 break;
2705 case 'lt':
2706 b = stack.pop();
2707 a = stack.pop();
2708 stack.push(a < b);
2709 break;
2710 case 'mod':
2711 b = stack.pop();
2712 a = stack.pop();
2713 stack.push(a % b);
2714 break;
2715 case 'mul':
2716 b = stack.pop();
2717 a = stack.pop();
2718 stack.push(a * b);
2719 break;
2720 case 'ne':
2721 b = stack.pop();
2722 a = stack.pop();
2723 stack.push(a != b);
2724 break;
2725 case 'neg':
2726 a = stack.pop();
2727 stack.push(-b);
2728 break;
2729 case 'not':
2730 a = stack.pop();
2731 if (isBool(a) && isBool(b)) {
2732 stack.push(a && b);
2733 } else {
2734 stack.push(a & b);
2735 }
2736 break;
2737 case 'or':
2738 b = stack.pop();
2739 a = stack.pop();
2740 if (isBool(a) && isBool(b)) {
2741 stack.push(a || b);
2742 } else {
2743 stack.push(a | b);
2744 }
2745 break;
2746 case 'pop':
2747 stack.pop();
2748 break;
2749 case 'roll':
2750 b = stack.pop();
2751 a = stack.pop();
2752 stack.roll(a, b);
2753 break;
2754 case 'round':
2755 a = stack.pop();
2756 stack.push(Math.round(a));
2757 break;
2758 case 'sin':
2759 a = stack.pop();
2760 stack.push(Math.sin(a));
2761 break;
2762 case 'sqrt':
2763 a = stack.pop();
2764 stack.push(Math.sqrt(a));
2765 break;
2766 case 'sub':
2767 b = stack.pop();
2768 a = stack.pop();
2769 stack.push(a - b);
2770 break;
2771 case 'true':
2772 stack.push(true);
2773 break;
2774 case 'truncate':
2775 a = stack.pop();
2776 a = a < 0 ? Math.ceil(a) : Math.floor(a);
2777 stack.push(a);
2778 break;
2779 case 'xor':
2780 b = stack.pop();
2781 a = stack.pop();
2782 if (isBool(a) && isBool(b)) {
2783 stack.push(a != b);
2784 } else {
2785 stack.push(a ^ b);
2786 }
2787 break;
2788 default:
2789 error('Unknown operator ' + operator);
2790 break;
2791 }
2792 }
2793 return stack.stack;
2794 }
2795 };
2796 return PostScriptEvaluator;
2797 })();
2800 var HIGHLIGHT_OFFSET = 4; // px
2801 var SUPPORTED_TYPES = ['Link', 'Text', 'Widget'];
2803 var Annotation = (function AnnotationClosure() {
2804 // 12.5.5: Algorithm: Appearance streams
2805 function getTransformMatrix(rect, bbox, matrix) {
2806 var bounds = Util.getAxialAlignedBoundingBox(bbox, matrix);
2807 var minX = bounds[0];
2808 var minY = bounds[1];
2809 var maxX = bounds[2];
2810 var maxY = bounds[3];
2812 if (minX === maxX || minY === maxY) {
2813 // From real-life file, bbox was [0, 0, 0, 0]. In this case,
2814 // just apply the transform for rect
2815 return [1, 0, 0, 1, rect[0], rect[1]];
2816 }
2818 var xRatio = (rect[2] - rect[0]) / (maxX - minX);
2819 var yRatio = (rect[3] - rect[1]) / (maxY - minY);
2820 return [
2821 xRatio,
2822 0,
2823 0,
2824 yRatio,
2825 rect[0] - minX * xRatio,
2826 rect[1] - minY * yRatio
2827 ];
2828 }
2830 function getDefaultAppearance(dict) {
2831 var appearanceState = dict.get('AP');
2832 if (!isDict(appearanceState)) {
2833 return;
2834 }
2836 var appearance;
2837 var appearances = appearanceState.get('N');
2838 if (isDict(appearances)) {
2839 var as = dict.get('AS');
2840 if (as && appearances.has(as.name)) {
2841 appearance = appearances.get(as.name);
2842 }
2843 } else {
2844 appearance = appearances;
2845 }
2846 return appearance;
2847 }
2849 function Annotation(params) {
2850 if (params.data) {
2851 this.data = params.data;
2852 return;
2853 }
2855 var dict = params.dict;
2856 var data = this.data = {};
2858 data.subtype = dict.get('Subtype').name;
2859 var rect = dict.get('Rect') || [0, 0, 0, 0];
2860 data.rect = Util.normalizeRect(rect);
2861 data.annotationFlags = dict.get('F');
2863 var color = dict.get('C');
2864 if (isArray(color) && color.length === 3) {
2865 // TODO(mack): currently only supporting rgb; need support different
2866 // colorspaces
2867 data.color = color;
2868 } else {
2869 data.color = [0, 0, 0];
2870 }
2872 // Some types of annotations have border style dict which has more
2873 // info than the border array
2874 if (dict.has('BS')) {
2875 var borderStyle = dict.get('BS');
2876 data.borderWidth = borderStyle.has('W') ? borderStyle.get('W') : 1;
2877 } else {
2878 var borderArray = dict.get('Border') || [0, 0, 1];
2879 data.borderWidth = borderArray[2] || 0;
2881 // TODO: implement proper support for annotations with line dash patterns.
2882 var dashArray = borderArray[3];
2883 if (data.borderWidth > 0 && dashArray && isArray(dashArray)) {
2884 var dashArrayLength = dashArray.length;
2885 if (dashArrayLength > 0) {
2886 // According to the PDF specification: the elements in a dashArray
2887 // shall be numbers that are nonnegative and not all equal to zero.
2888 var isInvalid = false;
2889 var numPositive = 0;
2890 for (var i = 0; i < dashArrayLength; i++) {
2891 var validNumber = (+dashArray[i] >= 0);
2892 if (!validNumber) {
2893 isInvalid = true;
2894 break;
2895 } else if (dashArray[i] > 0) {
2896 numPositive++;
2897 }
2898 }
2899 if (isInvalid || numPositive === 0) {
2900 data.borderWidth = 0;
2901 }
2902 }
2903 }
2904 }
2906 this.appearance = getDefaultAppearance(dict);
2907 data.hasAppearance = !!this.appearance;
2908 data.id = params.ref.num;
2909 }
2911 Annotation.prototype = {
2913 getData: function Annotation_getData() {
2914 return this.data;
2915 },
2917 hasHtml: function Annotation_hasHtml() {
2918 return false;
2919 },
2921 getHtmlElement: function Annotation_getHtmlElement(commonObjs) {
2922 throw new NotImplementedException(
2923 'getHtmlElement() should be implemented in subclass');
2924 },
2926 // TODO(mack): Remove this, it's not really that helpful.
2927 getEmptyContainer: function Annotation_getEmptyContainer(tagName, rect,
2928 borderWidth) {
2929 assert(!isWorker,
2930 'getEmptyContainer() should be called from main thread');
2932 var bWidth = borderWidth || 0;
2934 rect = rect || this.data.rect;
2935 var element = document.createElement(tagName);
2936 element.style.borderWidth = bWidth + 'px';
2937 var width = rect[2] - rect[0] - 2 * bWidth;
2938 var height = rect[3] - rect[1] - 2 * bWidth;
2939 element.style.width = width + 'px';
2940 element.style.height = height + 'px';
2941 return element;
2942 },
2944 isInvisible: function Annotation_isInvisible() {
2945 var data = this.data;
2946 if (data && SUPPORTED_TYPES.indexOf(data.subtype) !== -1) {
2947 return false;
2948 } else {
2949 return !!(data &&
2950 data.annotationFlags && // Default: not invisible
2951 data.annotationFlags & 0x1); // Invisible
2952 }
2953 },
2955 isViewable: function Annotation_isViewable() {
2956 var data = this.data;
2957 return !!(!this.isInvisible() &&
2958 data &&
2959 (!data.annotationFlags ||
2960 !(data.annotationFlags & 0x22)) && // Hidden or NoView
2961 data.rect); // rectangle is nessessary
2962 },
2964 isPrintable: function Annotation_isPrintable() {
2965 var data = this.data;
2966 return !!(!this.isInvisible() &&
2967 data &&
2968 data.annotationFlags && // Default: not printable
2969 data.annotationFlags & 0x4 && // Print
2970 data.rect); // rectangle is nessessary
2971 },
2973 loadResources: function(keys) {
2974 var promise = new LegacyPromise();
2975 this.appearance.dict.getAsync('Resources').then(function(resources) {
2976 if (!resources) {
2977 promise.resolve();
2978 return;
2979 }
2980 var objectLoader = new ObjectLoader(resources.map,
2981 keys,
2982 resources.xref);
2983 objectLoader.load().then(function() {
2984 promise.resolve(resources);
2985 });
2986 }.bind(this));
2988 return promise;
2989 },
2991 getOperatorList: function Annotation_getOperatorList(evaluator) {
2993 var promise = new LegacyPromise();
2995 if (!this.appearance) {
2996 promise.resolve(new OperatorList());
2997 return promise;
2998 }
3000 var data = this.data;
3002 var appearanceDict = this.appearance.dict;
3003 var resourcesPromise = this.loadResources([
3004 'ExtGState',
3005 'ColorSpace',
3006 'Pattern',
3007 'Shading',
3008 'XObject',
3009 'Font'
3010 // ProcSet
3011 // Properties
3012 ]);
3013 var bbox = appearanceDict.get('BBox') || [0, 0, 1, 1];
3014 var matrix = appearanceDict.get('Matrix') || [1, 0, 0, 1, 0 ,0];
3015 var transform = getTransformMatrix(data.rect, bbox, matrix);
3017 resourcesPromise.then(function(resources) {
3018 var opList = new OperatorList();
3019 opList.addOp(OPS.beginAnnotation, [data.rect, transform, matrix]);
3020 evaluator.getOperatorList(this.appearance, resources, opList);
3021 opList.addOp(OPS.endAnnotation, []);
3022 promise.resolve(opList);
3024 this.appearance.reset();
3025 }.bind(this));
3027 return promise;
3028 }
3029 };
3031 Annotation.getConstructor =
3032 function Annotation_getConstructor(subtype, fieldType) {
3034 if (!subtype) {
3035 return;
3036 }
3038 // TODO(mack): Implement FreeText annotations
3039 if (subtype === 'Link') {
3040 return LinkAnnotation;
3041 } else if (subtype === 'Text') {
3042 return TextAnnotation;
3043 } else if (subtype === 'Widget') {
3044 if (!fieldType) {
3045 return;
3046 }
3048 if (fieldType === 'Tx') {
3049 return TextWidgetAnnotation;
3050 } else {
3051 return WidgetAnnotation;
3052 }
3053 } else {
3054 return Annotation;
3055 }
3056 };
3058 // TODO(mack): Support loading annotation from data
3059 Annotation.fromData = function Annotation_fromData(data) {
3060 var subtype = data.subtype;
3061 var fieldType = data.fieldType;
3062 var Constructor = Annotation.getConstructor(subtype, fieldType);
3063 if (Constructor) {
3064 return new Constructor({ data: data });
3065 }
3066 };
3068 Annotation.fromRef = function Annotation_fromRef(xref, ref) {
3070 var dict = xref.fetchIfRef(ref);
3071 if (!isDict(dict)) {
3072 return;
3073 }
3075 var subtype = dict.get('Subtype');
3076 subtype = isName(subtype) ? subtype.name : '';
3077 if (!subtype) {
3078 return;
3079 }
3081 var fieldType = Util.getInheritableProperty(dict, 'FT');
3082 fieldType = isName(fieldType) ? fieldType.name : '';
3084 var Constructor = Annotation.getConstructor(subtype, fieldType);
3085 if (!Constructor) {
3086 return;
3087 }
3089 var params = {
3090 dict: dict,
3091 ref: ref,
3092 };
3094 var annotation = new Constructor(params);
3096 if (annotation.isViewable() || annotation.isPrintable()) {
3097 return annotation;
3098 } else {
3099 warn('unimplemented annotation type: ' + subtype);
3100 }
3101 };
3103 Annotation.appendToOperatorList = function Annotation_appendToOperatorList(
3104 annotations, opList, pdfManager, partialEvaluator, intent) {
3106 function reject(e) {
3107 annotationsReadyPromise.reject(e);
3108 }
3110 var annotationsReadyPromise = new LegacyPromise();
3112 var annotationPromises = [];
3113 for (var i = 0, n = annotations.length; i < n; ++i) {
3114 if (intent === 'display' && annotations[i].isViewable() ||
3115 intent === 'print' && annotations[i].isPrintable()) {
3116 annotationPromises.push(
3117 annotations[i].getOperatorList(partialEvaluator));
3118 }
3119 }
3120 Promise.all(annotationPromises).then(function(datas) {
3121 opList.addOp(OPS.beginAnnotations, []);
3122 for (var i = 0, n = datas.length; i < n; ++i) {
3123 var annotOpList = datas[i];
3124 opList.addOpList(annotOpList);
3125 }
3126 opList.addOp(OPS.endAnnotations, []);
3127 annotationsReadyPromise.resolve();
3128 }, reject);
3130 return annotationsReadyPromise;
3131 };
3133 return Annotation;
3134 })();
3135 PDFJS.Annotation = Annotation;
3138 var WidgetAnnotation = (function WidgetAnnotationClosure() {
3140 function WidgetAnnotation(params) {
3141 Annotation.call(this, params);
3143 if (params.data) {
3144 return;
3145 }
3147 var dict = params.dict;
3148 var data = this.data;
3150 data.fieldValue = stringToPDFString(
3151 Util.getInheritableProperty(dict, 'V') || '');
3152 data.alternativeText = stringToPDFString(dict.get('TU') || '');
3153 data.defaultAppearance = Util.getInheritableProperty(dict, 'DA') || '';
3154 var fieldType = Util.getInheritableProperty(dict, 'FT');
3155 data.fieldType = isName(fieldType) ? fieldType.name : '';
3156 data.fieldFlags = Util.getInheritableProperty(dict, 'Ff') || 0;
3157 this.fieldResources = Util.getInheritableProperty(dict, 'DR') || Dict.empty;
3159 // Building the full field name by collecting the field and
3160 // its ancestors 'T' data and joining them using '.'.
3161 var fieldName = [];
3162 var namedItem = dict;
3163 var ref = params.ref;
3164 while (namedItem) {
3165 var parent = namedItem.get('Parent');
3166 var parentRef = namedItem.getRaw('Parent');
3167 var name = namedItem.get('T');
3168 if (name) {
3169 fieldName.unshift(stringToPDFString(name));
3170 } else {
3171 // The field name is absent, that means more than one field
3172 // with the same name may exist. Replacing the empty name
3173 // with the '`' plus index in the parent's 'Kids' array.
3174 // This is not in the PDF spec but necessary to id the
3175 // the input controls.
3176 var kids = parent.get('Kids');
3177 var j, jj;
3178 for (j = 0, jj = kids.length; j < jj; j++) {
3179 var kidRef = kids[j];
3180 if (kidRef.num == ref.num && kidRef.gen == ref.gen) {
3181 break;
3182 }
3183 }
3184 fieldName.unshift('`' + j);
3185 }
3186 namedItem = parent;
3187 ref = parentRef;
3188 }
3189 data.fullName = fieldName.join('.');
3190 }
3192 var parent = Annotation.prototype;
3193 Util.inherit(WidgetAnnotation, Annotation, {
3194 isViewable: function WidgetAnnotation_isViewable() {
3195 if (this.data.fieldType === 'Sig') {
3196 warn('unimplemented annotation type: Widget signature');
3197 return false;
3198 }
3200 return parent.isViewable.call(this);
3201 }
3202 });
3204 return WidgetAnnotation;
3205 })();
3207 var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
3208 function TextWidgetAnnotation(params) {
3209 WidgetAnnotation.call(this, params);
3211 if (params.data) {
3212 return;
3213 }
3215 this.data.textAlignment = Util.getInheritableProperty(params.dict, 'Q');
3216 }
3218 // TODO(mack): This dupes some of the logic in CanvasGraphics.setFont()
3219 function setTextStyles(element, item, fontObj) {
3221 var style = element.style;
3222 style.fontSize = item.fontSize + 'px';
3223 style.direction = item.fontDirection < 0 ? 'rtl': 'ltr';
3225 if (!fontObj) {
3226 return;
3227 }
3229 style.fontWeight = fontObj.black ?
3230 (fontObj.bold ? 'bolder' : 'bold') :
3231 (fontObj.bold ? 'bold' : 'normal');
3232 style.fontStyle = fontObj.italic ? 'italic' : 'normal';
3234 var fontName = fontObj.loadedName;
3235 var fontFamily = fontName ? '"' + fontName + '", ' : '';
3236 // Use a reasonable default font if the font doesn't specify a fallback
3237 var fallbackName = fontObj.fallbackName || 'Helvetica, sans-serif';
3238 style.fontFamily = fontFamily + fallbackName;
3239 }
3242 Util.inherit(TextWidgetAnnotation, WidgetAnnotation, {
3243 hasHtml: function TextWidgetAnnotation_hasHtml() {
3244 return !this.data.hasAppearance && !!this.data.fieldValue;
3245 },
3247 getHtmlElement: function TextWidgetAnnotation_getHtmlElement(commonObjs) {
3248 assert(!isWorker, 'getHtmlElement() shall be called from main thread');
3250 var item = this.data;
3252 var element = this.getEmptyContainer('div');
3253 element.style.display = 'table';
3255 var content = document.createElement('div');
3256 content.textContent = item.fieldValue;
3257 var textAlignment = item.textAlignment;
3258 content.style.textAlign = ['left', 'center', 'right'][textAlignment];
3259 content.style.verticalAlign = 'middle';
3260 content.style.display = 'table-cell';
3262 var fontObj = item.fontRefName ?
3263 commonObjs.getData(item.fontRefName) : null;
3264 setTextStyles(content, item, fontObj);
3266 element.appendChild(content);
3268 return element;
3269 },
3271 getOperatorList: function TextWidgetAnnotation_getOperatorList(evaluator) {
3272 if (this.appearance) {
3273 return Annotation.prototype.getOperatorList.call(this, evaluator);
3274 }
3276 var promise = new LegacyPromise();
3277 var opList = new OperatorList();
3278 var data = this.data;
3280 // Even if there is an appearance stream, ignore it. This is the
3281 // behaviour used by Adobe Reader.
3283 var defaultAppearance = data.defaultAppearance;
3284 if (!defaultAppearance) {
3285 promise.resolve(opList);
3286 return promise;
3287 }
3289 // Include any font resources found in the default appearance
3291 var stream = new Stream(stringToBytes(defaultAppearance));
3292 evaluator.getOperatorList(stream, this.fieldResources, opList);
3293 var appearanceFnArray = opList.fnArray;
3294 var appearanceArgsArray = opList.argsArray;
3295 var fnArray = [];
3297 // TODO(mack): Add support for stroke color
3298 data.rgb = [0, 0, 0];
3299 // TODO THIS DOESN'T MAKE ANY SENSE SINCE THE fnArray IS EMPTY!
3300 for (var i = 0, n = fnArray.length; i < n; ++i) {
3301 var fnId = appearanceFnArray[i];
3302 var args = appearanceArgsArray[i];
3304 if (fnId === OPS.setFont) {
3305 data.fontRefName = args[0];
3306 var size = args[1];
3307 if (size < 0) {
3308 data.fontDirection = -1;
3309 data.fontSize = -size;
3310 } else {
3311 data.fontDirection = 1;
3312 data.fontSize = size;
3313 }
3314 } else if (fnId === OPS.setFillRGBColor) {
3315 data.rgb = args;
3316 } else if (fnId === OPS.setFillGray) {
3317 var rgbValue = args[0] * 255;
3318 data.rgb = [rgbValue, rgbValue, rgbValue];
3319 }
3320 }
3321 promise.resolve(opList);
3322 return promise;
3323 }
3324 });
3326 return TextWidgetAnnotation;
3327 })();
3329 var InteractiveAnnotation = (function InteractiveAnnotationClosure() {
3330 function InteractiveAnnotation(params) {
3331 Annotation.call(this, params);
3332 }
3334 Util.inherit(InteractiveAnnotation, Annotation, {
3335 hasHtml: function InteractiveAnnotation_hasHtml() {
3336 return true;
3337 },
3339 highlight: function InteractiveAnnotation_highlight() {
3340 if (this.highlightElement &&
3341 this.highlightElement.hasAttribute('hidden')) {
3342 this.highlightElement.removeAttribute('hidden');
3343 }
3344 },
3346 unhighlight: function InteractiveAnnotation_unhighlight() {
3347 if (this.highlightElement &&
3348 !this.highlightElement.hasAttribute('hidden')) {
3349 this.highlightElement.setAttribute('hidden', true);
3350 }
3351 },
3353 initContainer: function InteractiveAnnotation_initContainer() {
3355 var item = this.data;
3356 var rect = item.rect;
3358 var container = this.getEmptyContainer('section', rect, item.borderWidth);
3359 container.style.backgroundColor = item.color;
3361 var color = item.color;
3362 var rgb = [];
3363 for (var i = 0; i < 3; ++i) {
3364 rgb[i] = Math.round(color[i] * 255);
3365 }
3366 item.colorCssRgb = Util.makeCssRgb(rgb);
3368 var highlight = document.createElement('div');
3369 highlight.className = 'annotationHighlight';
3370 highlight.style.left = highlight.style.top = -HIGHLIGHT_OFFSET + 'px';
3371 highlight.style.right = highlight.style.bottom = -HIGHLIGHT_OFFSET + 'px';
3372 highlight.setAttribute('hidden', true);
3374 this.highlightElement = highlight;
3375 container.appendChild(this.highlightElement);
3377 return container;
3378 }
3379 });
3381 return InteractiveAnnotation;
3382 })();
3384 var TextAnnotation = (function TextAnnotationClosure() {
3385 function TextAnnotation(params) {
3386 InteractiveAnnotation.call(this, params);
3388 if (params.data) {
3389 return;
3390 }
3392 var dict = params.dict;
3393 var data = this.data;
3395 var content = dict.get('Contents');
3396 var title = dict.get('T');
3397 data.content = stringToPDFString(content || '');
3398 data.title = stringToPDFString(title || '');
3400 if (data.hasAppearance) {
3401 data.name = 'NoIcon';
3402 } else {
3403 data.name = dict.has('Name') ? dict.get('Name').name : 'Note';
3404 }
3406 if (dict.has('C')) {
3407 data.hasBgColor = true;
3408 }
3409 }
3411 var ANNOT_MIN_SIZE = 10;
3413 Util.inherit(TextAnnotation, InteractiveAnnotation, {
3415 getHtmlElement: function TextAnnotation_getHtmlElement(commonObjs) {
3416 assert(!isWorker, 'getHtmlElement() shall be called from main thread');
3418 var item = this.data;
3419 var rect = item.rect;
3421 // sanity check because of OOo-generated PDFs
3422 if ((rect[3] - rect[1]) < ANNOT_MIN_SIZE) {
3423 rect[3] = rect[1] + ANNOT_MIN_SIZE;
3424 }
3425 if ((rect[2] - rect[0]) < ANNOT_MIN_SIZE) {
3426 rect[2] = rect[0] + (rect[3] - rect[1]); // make it square
3427 }
3429 var container = this.initContainer();
3430 container.className = 'annotText';
3432 var image = document.createElement('img');
3433 image.style.height = container.style.height;
3434 image.style.width = container.style.width;
3435 var iconName = item.name;
3436 image.src = PDFJS.imageResourcesPath + 'annotation-' +
3437 iconName.toLowerCase() + '.svg';
3438 image.alt = '[{{type}} Annotation]';
3439 image.dataset.l10nId = 'text_annotation_type';
3440 image.dataset.l10nArgs = JSON.stringify({type: iconName});
3442 var contentWrapper = document.createElement('div');
3443 contentWrapper.className = 'annotTextContentWrapper';
3444 contentWrapper.style.left = Math.floor(rect[2] - rect[0] + 5) + 'px';
3445 contentWrapper.style.top = '-10px';
3447 var content = document.createElement('div');
3448 content.className = 'annotTextContent';
3449 content.setAttribute('hidden', true);
3451 var i, ii;
3452 if (item.hasBgColor) {
3453 var color = item.color;
3454 var rgb = [];
3455 for (i = 0; i < 3; ++i) {
3456 // Enlighten the color (70%)
3457 var c = Math.round(color[i] * 255);
3458 rgb[i] = Math.round((255 - c) * 0.7) + c;
3459 }
3460 content.style.backgroundColor = Util.makeCssRgb(rgb);
3461 }
3463 var title = document.createElement('h1');
3464 var text = document.createElement('p');
3465 title.textContent = item.title;
3467 if (!item.content && !item.title) {
3468 content.setAttribute('hidden', true);
3469 } else {
3470 var e = document.createElement('span');
3471 var lines = item.content.split(/(?:\r\n?|\n)/);
3472 for (i = 0, ii = lines.length; i < ii; ++i) {
3473 var line = lines[i];
3474 e.appendChild(document.createTextNode(line));
3475 if (i < (ii - 1)) {
3476 e.appendChild(document.createElement('br'));
3477 }
3478 }
3479 text.appendChild(e);
3481 var pinned = false;
3483 var showAnnotation = function showAnnotation(pin) {
3484 if (pin) {
3485 pinned = true;
3486 }
3487 if (content.hasAttribute('hidden')) {
3488 container.style.zIndex += 1;
3489 content.removeAttribute('hidden');
3490 }
3491 };
3493 var hideAnnotation = function hideAnnotation(unpin) {
3494 if (unpin) {
3495 pinned = false;
3496 }
3497 if (!content.hasAttribute('hidden') && !pinned) {
3498 container.style.zIndex -= 1;
3499 content.setAttribute('hidden', true);
3500 }
3501 };
3503 var toggleAnnotation = function toggleAnnotation() {
3504 if (pinned) {
3505 hideAnnotation(true);
3506 } else {
3507 showAnnotation(true);
3508 }
3509 };
3511 image.addEventListener('click', function image_clickHandler() {
3512 toggleAnnotation();
3513 }, false);
3514 image.addEventListener('mouseover', function image_mouseOverHandler() {
3515 showAnnotation();
3516 }, false);
3517 image.addEventListener('mouseout', function image_mouseOutHandler() {
3518 hideAnnotation();
3519 }, false);
3521 content.addEventListener('click', function content_clickHandler() {
3522 hideAnnotation(true);
3523 }, false);
3524 }
3526 content.appendChild(title);
3527 content.appendChild(text);
3528 contentWrapper.appendChild(content);
3529 container.appendChild(image);
3530 container.appendChild(contentWrapper);
3532 return container;
3533 }
3534 });
3536 return TextAnnotation;
3537 })();
3539 var LinkAnnotation = (function LinkAnnotationClosure() {
3540 function LinkAnnotation(params) {
3541 InteractiveAnnotation.call(this, params);
3543 if (params.data) {
3544 return;
3545 }
3547 var dict = params.dict;
3548 var data = this.data;
3550 var action = dict.get('A');
3551 if (action) {
3552 var linkType = action.get('S').name;
3553 if (linkType === 'URI') {
3554 var url = action.get('URI');
3555 if (isName(url)) {
3556 // Some bad PDFs do not put parentheses around relative URLs.
3557 url = '/' + url.name;
3558 } else if (url) {
3559 url = addDefaultProtocolToUrl(url);
3560 }
3561 // TODO: pdf spec mentions urls can be relative to a Base
3562 // entry in the dictionary.
3563 if (!isValidUrl(url, false)) {
3564 url = '';
3565 }
3566 data.url = url;
3567 } else if (linkType === 'GoTo') {
3568 data.dest = action.get('D');
3569 } else if (linkType === 'GoToR') {
3570 var urlDict = action.get('F');
3571 if (isDict(urlDict)) {
3572 // We assume that the 'url' is a Filspec dictionary
3573 // and fetch the url without checking any further
3574 url = urlDict.get('F') || '';
3575 }
3577 // TODO: pdf reference says that GoToR
3578 // can also have 'NewWindow' attribute
3579 if (!isValidUrl(url, false)) {
3580 url = '';
3581 }
3582 data.url = url;
3583 data.dest = action.get('D');
3584 } else if (linkType === 'Named') {
3585 data.action = action.get('N').name;
3586 } else {
3587 warn('unrecognized link type: ' + linkType);
3588 }
3589 } else if (dict.has('Dest')) {
3590 // simple destination link
3591 var dest = dict.get('Dest');
3592 data.dest = isName(dest) ? dest.name : dest;
3593 }
3594 }
3596 // Lets URLs beginning with 'www.' default to using the 'http://' protocol.
3597 function addDefaultProtocolToUrl(url) {
3598 if (url && url.indexOf('www.') === 0) {
3599 return ('http://' + url);
3600 }
3601 return url;
3602 }
3604 Util.inherit(LinkAnnotation, InteractiveAnnotation, {
3605 hasOperatorList: function LinkAnnotation_hasOperatorList() {
3606 return false;
3607 },
3609 getHtmlElement: function LinkAnnotation_getHtmlElement(commonObjs) {
3611 var container = this.initContainer();
3612 container.className = 'annotLink';
3614 var item = this.data;
3616 container.style.borderColor = item.colorCssRgb;
3617 container.style.borderStyle = 'solid';
3619 var link = document.createElement('a');
3620 link.href = link.title = this.data.url || '';
3622 container.appendChild(link);
3624 return container;
3625 }
3626 });
3628 return LinkAnnotation;
3629 })();
3632 /**
3633 * The maximum allowed image size in total pixels e.g. width * height. Images
3634 * above this value will not be drawn. Use -1 for no limit.
3635 * @var {number}
3636 */
3637 PDFJS.maxImageSize = (PDFJS.maxImageSize === undefined ?
3638 -1 : PDFJS.maxImageSize);
3640 /**
3641 * The url of where the predefined Adobe CMaps are located. Include trailing
3642 * slash.
3643 * @var {string}
3644 */
3645 PDFJS.cMapUrl = (PDFJS.cMapUrl === undefined ? null : PDFJS.cMapUrl);
3647 /**
3648 * Specifies if CMaps are binary packed.
3649 * @var {boolean}
3650 */
3651 PDFJS.cMapPacked = PDFJS.cMapPacked === undefined ? false : PDFJS.cMapPacked;
3653 /*
3654 * By default fonts are converted to OpenType fonts and loaded via font face
3655 * rules. If disabled, the font will be rendered using a built in font renderer
3656 * that constructs the glyphs with primitive path commands.
3657 * @var {boolean}
3658 */
3659 PDFJS.disableFontFace = (PDFJS.disableFontFace === undefined ?
3660 false : PDFJS.disableFontFace);
3662 /**
3663 * Path for image resources, mainly for annotation icons. Include trailing
3664 * slash.
3665 * @var {string}
3666 */
3667 PDFJS.imageResourcesPath = (PDFJS.imageResourcesPath === undefined ?
3668 '' : PDFJS.imageResourcesPath);
3670 /**
3671 * Disable the web worker and run all code on the main thread. This will happen
3672 * automatically if the browser doesn't support workers or sending typed arrays
3673 * to workers.
3674 * @var {boolean}
3675 */
3676 PDFJS.disableWorker = (PDFJS.disableWorker === undefined ?
3677 false : PDFJS.disableWorker);
3679 /**
3680 * Path and filename of the worker file. Required when the worker is enabled in
3681 * development mode. If unspecified in the production build, the worker will be
3682 * loaded based on the location of the pdf.js file.
3683 * @var {string}
3684 */
3685 PDFJS.workerSrc = (PDFJS.workerSrc === undefined ? null : PDFJS.workerSrc);
3687 /**
3688 * Disable range request loading of PDF files. When enabled and if the server
3689 * supports partial content requests then the PDF will be fetched in chunks.
3690 * Enabled (false) by default.
3691 * @var {boolean}
3692 */
3693 PDFJS.disableRange = (PDFJS.disableRange === undefined ?
3694 false : PDFJS.disableRange);
3696 /**
3697 * Disable pre-fetching of PDF file data. When range requests are enabled PDF.js
3698 * will automatically keep fetching more data even if it isn't needed to display
3699 * the current page. This default behavior can be disabled.
3700 * @var {boolean}
3701 */
3702 PDFJS.disableAutoFetch = (PDFJS.disableAutoFetch === undefined ?
3703 false : PDFJS.disableAutoFetch);
3705 /**
3706 * Enables special hooks for debugging PDF.js.
3707 * @var {boolean}
3708 */
3709 PDFJS.pdfBug = (PDFJS.pdfBug === undefined ? false : PDFJS.pdfBug);
3711 /**
3712 * Enables transfer usage in postMessage for ArrayBuffers.
3713 * @var {boolean}
3714 */
3715 PDFJS.postMessageTransfers = (PDFJS.postMessageTransfers === undefined ?
3716 true : PDFJS.postMessageTransfers);
3718 /**
3719 * Disables URL.createObjectURL usage.
3720 * @var {boolean}
3721 */
3722 PDFJS.disableCreateObjectURL = (PDFJS.disableCreateObjectURL === undefined ?
3723 false : PDFJS.disableCreateObjectURL);
3725 /**
3726 * Disables WebGL usage.
3727 * @var {boolean}
3728 */
3729 PDFJS.disableWebGL = (PDFJS.disableWebGL === undefined ?
3730 true : PDFJS.disableWebGL);
3732 /**
3733 * Controls the logging level.
3734 * The constants from PDFJS.VERBOSITY_LEVELS should be used:
3735 * - errors
3736 * - warnings [default]
3737 * - infos
3738 * @var {number}
3739 */
3740 PDFJS.verbosity = (PDFJS.verbosity === undefined ?
3741 PDFJS.VERBOSITY_LEVELS.warnings : PDFJS.verbosity);
3743 /**
3744 * Document initialization / loading parameters object.
3745 *
3746 * @typedef {Object} DocumentInitParameters
3747 * @property {string} url - The URL of the PDF.
3748 * @property {TypedArray} data - A typed array with PDF data.
3749 * @property {Object} httpHeaders - Basic authentication headers.
3750 * @property {boolean} withCredentials - Indicates whether or not cross-site
3751 * Access-Control requests should be made using credentials such as cookies
3752 * or authorization headers. The default is false.
3753 * @property {string} password - For decrypting password-protected PDFs.
3754 * @property {TypedArray} initialData - A typed array with the first portion or
3755 * all of the pdf data. Used by the extension since some data is already
3756 * loaded before the switch to range requests.
3757 */
3759 /**
3760 * This is the main entry point for loading a PDF and interacting with it.
3761 * NOTE: If a URL is used to fetch the PDF data a standard XMLHttpRequest(XHR)
3762 * is used, which means it must follow the same origin rules that any XHR does
3763 * e.g. No cross domain requests without CORS.
3764 *
3765 * @param {string|TypedArray|DocumentInitParameters} source Can be a url to
3766 * where a PDF is located, a typed array (Uint8Array) already populated with
3767 * data or parameter object.
3768 *
3769 * @param {Object} pdfDataRangeTransport is optional. It is used if you want
3770 * to manually serve range requests for data in the PDF. See viewer.js for
3771 * an example of pdfDataRangeTransport's interface.
3772 *
3773 * @param {function} passwordCallback is optional. It is used to request a
3774 * password if wrong or no password was provided. The callback receives two
3775 * parameters: function that needs to be called with new password and reason
3776 * (see {PasswordResponses}).
3777 *
3778 * @return {Promise} A promise that is resolved with {@link PDFDocumentProxy}
3779 * object.
3780 */
3781 PDFJS.getDocument = function getDocument(source,
3782 pdfDataRangeTransport,
3783 passwordCallback,
3784 progressCallback) {
3785 var workerInitializedPromise, workerReadyPromise, transport;
3787 if (typeof source === 'string') {
3788 source = { url: source };
3789 } else if (isArrayBuffer(source)) {
3790 source = { data: source };
3791 } else if (typeof source !== 'object') {
3792 error('Invalid parameter in getDocument, need either Uint8Array, ' +
3793 'string or a parameter object');
3794 }
3796 if (!source.url && !source.data) {
3797 error('Invalid parameter array, need either .data or .url');
3798 }
3800 // copy/use all keys as is except 'url' -- full path is required
3801 var params = {};
3802 for (var key in source) {
3803 if (key === 'url' && typeof window !== 'undefined') {
3804 params[key] = combineUrl(window.location.href, source[key]);
3805 continue;
3806 }
3807 params[key] = source[key];
3808 }
3810 workerInitializedPromise = new PDFJS.LegacyPromise();
3811 workerReadyPromise = new PDFJS.LegacyPromise();
3812 transport = new WorkerTransport(workerInitializedPromise, workerReadyPromise,
3813 pdfDataRangeTransport, progressCallback);
3814 workerInitializedPromise.then(function transportInitialized() {
3815 transport.passwordCallback = passwordCallback;
3816 transport.fetchDocument(params);
3817 });
3818 return workerReadyPromise;
3819 };
3821 /**
3822 * Proxy to a PDFDocument in the worker thread. Also, contains commonly used
3823 * properties that can be read synchronously.
3824 * @class
3825 */
3826 var PDFDocumentProxy = (function PDFDocumentProxyClosure() {
3827 function PDFDocumentProxy(pdfInfo, transport) {
3828 this.pdfInfo = pdfInfo;
3829 this.transport = transport;
3830 }
3831 PDFDocumentProxy.prototype = /** @lends PDFDocumentProxy.prototype */ {
3832 /**
3833 * @return {number} Total number of pages the PDF contains.
3834 */
3835 get numPages() {
3836 return this.pdfInfo.numPages;
3837 },
3838 /**
3839 * @return {string} A unique ID to identify a PDF. Not guaranteed to be
3840 * unique.
3841 */
3842 get fingerprint() {
3843 return this.pdfInfo.fingerprint;
3844 },
3845 /**
3846 * @param {number} pageNumber The page number to get. The first page is 1.
3847 * @return {Promise} A promise that is resolved with a {@link PDFPageProxy}
3848 * object.
3849 */
3850 getPage: function PDFDocumentProxy_getPage(pageNumber) {
3851 return this.transport.getPage(pageNumber);
3852 },
3853 /**
3854 * @param {{num: number, gen: number}} ref The page reference. Must have
3855 * the 'num' and 'gen' properties.
3856 * @return {Promise} A promise that is resolved with the page index that is
3857 * associated with the reference.
3858 */
3859 getPageIndex: function PDFDocumentProxy_getPageIndex(ref) {
3860 return this.transport.getPageIndex(ref);
3861 },
3862 /**
3863 * @return {Promise} A promise that is resolved with a lookup table for
3864 * mapping named destinations to reference numbers.
3865 */
3866 getDestinations: function PDFDocumentProxy_getDestinations() {
3867 return this.transport.getDestinations();
3868 },
3869 /**
3870 * @return {Promise} A promise that is resolved with a lookup table for
3871 * mapping named attachments to their content.
3872 */
3873 getAttachments: function PDFDocumentProxy_getAttachments() {
3874 return this.transport.getAttachments();
3875 },
3876 /**
3877 * @return {Promise} A promise that is resolved with an array of all the
3878 * JavaScript strings in the name tree.
3879 */
3880 getJavaScript: function PDFDocumentProxy_getJavaScript() {
3881 var promise = new PDFJS.LegacyPromise();
3882 var js = this.pdfInfo.javaScript;
3883 promise.resolve(js);
3884 return promise;
3885 },
3886 /**
3887 * @return {Promise} A promise that is resolved with an {Array} that is a
3888 * tree outline (if it has one) of the PDF. The tree is in the format of:
3889 * [
3890 * {
3891 * title: string,
3892 * bold: boolean,
3893 * italic: boolean,
3894 * color: rgb array,
3895 * dest: dest obj,
3896 * items: array of more items like this
3897 * },
3898 * ...
3899 * ].
3900 */
3901 getOutline: function PDFDocumentProxy_getOutline() {
3902 var promise = new PDFJS.LegacyPromise();
3903 var outline = this.pdfInfo.outline;
3904 promise.resolve(outline);
3905 return promise;
3906 },
3907 /**
3908 * @return {Promise} A promise that is resolved with an {Object} that has
3909 * info and metadata properties. Info is an {Object} filled with anything
3910 * available in the information dictionary and similarly metadata is a
3911 * {Metadata} object with information from the metadata section of the PDF.
3912 */
3913 getMetadata: function PDFDocumentProxy_getMetadata() {
3914 var promise = new PDFJS.LegacyPromise();
3915 var info = this.pdfInfo.info;
3916 var metadata = this.pdfInfo.metadata;
3917 promise.resolve({
3918 info: info,
3919 metadata: (metadata ? new PDFJS.Metadata(metadata) : null)
3920 });
3921 return promise;
3922 },
3923 /**
3924 * @return {Promise} A promise that is resolved with a TypedArray that has
3925 * the raw data from the PDF.
3926 */
3927 getData: function PDFDocumentProxy_getData() {
3928 var promise = new PDFJS.LegacyPromise();
3929 this.transport.getData(promise);
3930 return promise;
3931 },
3932 /**
3933 * @return {Promise} A promise that is resolved when the document's data
3934 * is loaded. It is resolved with an {Object} that contains the length
3935 * property that indicates size of the PDF data in bytes.
3936 */
3937 getDownloadInfo: function PDFDocumentProxy_getDownloadInfo() {
3938 return this.transport.downloadInfoPromise;
3939 },
3940 /**
3941 * Cleans up resources allocated by the document, e.g. created @font-face.
3942 */
3943 cleanup: function PDFDocumentProxy_cleanup() {
3944 this.transport.startCleanup();
3945 },
3946 /**
3947 * Destroys current document instance and terminates worker.
3948 */
3949 destroy: function PDFDocumentProxy_destroy() {
3950 this.transport.destroy();
3951 }
3952 };
3953 return PDFDocumentProxy;
3954 })();
3956 /**
3957 * Page text content.
3958 *
3959 * @typedef {Object} TextContent
3960 * @property {array} items - array of {@link TextItem}
3961 * @property {Object} styles - {@link TextStyles} objects, indexed by font
3962 * name.
3963 */
3965 /**
3966 * Page text content part.
3967 *
3968 * @typedef {Object} TextItem
3969 * @property {string} str - text content.
3970 * @property {string} dir - text direction: 'ttb', 'ltr' or 'rtl'.
3971 * @property {array} transform - transformation matrix.
3972 * @property {number} width - width in device space.
3973 * @property {number} height - height in device space.
3974 * @property {string} fontName - font name used by pdf.js for converted font.
3975 */
3977 /**
3978 * Text style.
3979 *
3980 * @typedef {Object} TextStyle
3981 * @property {number} ascent - font ascent.
3982 * @property {number} descent - font descent.
3983 * @property {boolean} vertical - text is in vertical mode.
3984 * @property {string} fontFamily - possible font family
3985 */
3987 /**
3988 * Page render parameters.
3989 *
3990 * @typedef {Object} RenderParameters
3991 * @property {Object} canvasContext - A 2D context of a DOM Canvas object.
3992 * @property {PageViewport} viewport - Rendering viewport obtained by
3993 * calling of PDFPage.getViewport method.
3994 * @property {string} intent - Rendering intent, can be 'display' or 'print'
3995 * (default value is 'display').
3996 * @property {Object} imageLayer - (optional) An object that has beginLayout,
3997 * endLayout and appendImage functions.
3998 * @property {function} continueCallback - (optional) A function that will be
3999 * called each time the rendering is paused. To continue
4000 * rendering call the function that is the first argument
4001 * to the callback.
4002 */
4004 /**
4005 * Proxy to a PDFPage in the worker thread.
4006 * @class
4007 */
4008 var PDFPageProxy = (function PDFPageProxyClosure() {
4009 function PDFPageProxy(pageInfo, transport) {
4010 this.pageInfo = pageInfo;
4011 this.transport = transport;
4012 this.stats = new StatTimer();
4013 this.stats.enabled = !!globalScope.PDFJS.enableStats;
4014 this.commonObjs = transport.commonObjs;
4015 this.objs = new PDFObjects();
4016 this.cleanupAfterRender = false;
4017 this.pendingDestroy = false;
4018 this.intentStates = {};
4019 }
4020 PDFPageProxy.prototype = /** @lends PDFPageProxy.prototype */ {
4021 /**
4022 * @return {number} Page number of the page. First page is 1.
4023 */
4024 get pageNumber() {
4025 return this.pageInfo.pageIndex + 1;
4026 },
4027 /**
4028 * @return {number} The number of degrees the page is rotated clockwise.
4029 */
4030 get rotate() {
4031 return this.pageInfo.rotate;
4032 },
4033 /**
4034 * @return {Object} The reference that points to this page. It has 'num' and
4035 * 'gen' properties.
4036 */
4037 get ref() {
4038 return this.pageInfo.ref;
4039 },
4040 /**
4041 * @return {Array} An array of the visible portion of the PDF page in the
4042 * user space units - [x1, y1, x2, y2].
4043 */
4044 get view() {
4045 return this.pageInfo.view;
4046 },
4047 /**
4048 * @param {number} scale The desired scale of the viewport.
4049 * @param {number} rotate Degrees to rotate the viewport. If omitted this
4050 * defaults to the page rotation.
4051 * @return {PageViewport} Contains 'width' and 'height' properties along
4052 * with transforms required for rendering.
4053 */
4054 getViewport: function PDFPageProxy_getViewport(scale, rotate) {
4055 if (arguments.length < 2) {
4056 rotate = this.rotate;
4057 }
4058 return new PDFJS.PageViewport(this.view, scale, rotate, 0, 0);
4059 },
4060 /**
4061 * @return {Promise} A promise that is resolved with an {Array} of the
4062 * annotation objects.
4063 */
4064 getAnnotations: function PDFPageProxy_getAnnotations() {
4065 if (this.annotationsPromise) {
4066 return this.annotationsPromise;
4067 }
4069 var promise = new PDFJS.LegacyPromise();
4070 this.annotationsPromise = promise;
4071 this.transport.getAnnotations(this.pageInfo.pageIndex);
4072 return promise;
4073 },
4074 /**
4075 * Begins the process of rendering a page to the desired context.
4076 * @param {RenderParameters} params Page render parameters.
4077 * @return {RenderTask} An object that contains the promise, which
4078 * is resolved when the page finishes rendering.
4079 */
4080 render: function PDFPageProxy_render(params) {
4081 var stats = this.stats;
4082 stats.time('Overall');
4084 // If there was a pending destroy cancel it so no cleanup happens during
4085 // this call to render.
4086 this.pendingDestroy = false;
4088 var renderingIntent = ('intent' in params ?
4089 (params.intent == 'print' ? 'print' : 'display') : 'display');
4091 if (!this.intentStates[renderingIntent]) {
4092 this.intentStates[renderingIntent] = {};
4093 }
4094 var intentState = this.intentStates[renderingIntent];
4096 // If there is no displayReadyPromise yet, then the operatorList was never
4097 // requested before. Make the request and create the promise.
4098 if (!intentState.displayReadyPromise) {
4099 intentState.receivingOperatorList = true;
4100 intentState.displayReadyPromise = new LegacyPromise();
4101 intentState.operatorList = {
4102 fnArray: [],
4103 argsArray: [],
4104 lastChunk: false
4105 };
4107 this.stats.time('Page Request');
4108 this.transport.messageHandler.send('RenderPageRequest', {
4109 pageIndex: this.pageNumber - 1,
4110 intent: renderingIntent
4111 });
4112 }
4114 var internalRenderTask = new InternalRenderTask(complete, params,
4115 this.objs,
4116 this.commonObjs,
4117 intentState.operatorList,
4118 this.pageNumber);
4119 if (!intentState.renderTasks) {
4120 intentState.renderTasks = [];
4121 }
4122 intentState.renderTasks.push(internalRenderTask);
4123 var renderTask = new RenderTask(internalRenderTask);
4125 var self = this;
4126 intentState.displayReadyPromise.then(
4127 function pageDisplayReadyPromise(transparency) {
4128 if (self.pendingDestroy) {
4129 complete();
4130 return;
4131 }
4132 stats.time('Rendering');
4133 internalRenderTask.initalizeGraphics(transparency);
4134 internalRenderTask.operatorListChanged();
4135 },
4136 function pageDisplayReadPromiseError(reason) {
4137 complete(reason);
4138 }
4139 );
4141 function complete(error) {
4142 var i = intentState.renderTasks.indexOf(internalRenderTask);
4143 if (i >= 0) {
4144 intentState.renderTasks.splice(i, 1);
4145 }
4147 if (self.cleanupAfterRender) {
4148 self.pendingDestroy = true;
4149 }
4150 self._tryDestroy();
4152 if (error) {
4153 renderTask.promise.reject(error);
4154 } else {
4155 renderTask.promise.resolve();
4156 }
4157 stats.timeEnd('Rendering');
4158 stats.timeEnd('Overall');
4159 }
4161 return renderTask;
4162 },
4163 /**
4164 * @return {Promise} That is resolved a {@link TextContent}
4165 * object that represent the page text content.
4166 */
4167 getTextContent: function PDFPageProxy_getTextContent() {
4168 var promise = new PDFJS.LegacyPromise();
4169 this.transport.messageHandler.send('GetTextContent', {
4170 pageIndex: this.pageNumber - 1
4171 },
4172 function textContentCallback(textContent) {
4173 promise.resolve(textContent);
4174 }
4175 );
4176 return promise;
4177 },
4178 /**
4179 * Destroys resources allocated by the page.
4180 */
4181 destroy: function PDFPageProxy_destroy() {
4182 this.pendingDestroy = true;
4183 this._tryDestroy();
4184 },
4185 /**
4186 * For internal use only. Attempts to clean up if rendering is in a state
4187 * where that's possible.
4188 * @ignore
4189 */
4190 _tryDestroy: function PDFPageProxy__destroy() {
4191 if (!this.pendingDestroy ||
4192 Object.keys(this.intentStates).some(function(intent) {
4193 var intentState = this.intentStates[intent];
4194 return (intentState.renderTasks.length !== 0 ||
4195 intentState.receivingOperatorList);
4196 }, this)) {
4197 return;
4198 }
4200 Object.keys(this.intentStates).forEach(function(intent) {
4201 delete this.intentStates[intent];
4202 }, this);
4203 this.objs.clear();
4204 this.pendingDestroy = false;
4205 },
4206 /**
4207 * For internal use only.
4208 * @ignore
4209 */
4210 _startRenderPage: function PDFPageProxy_startRenderPage(transparency,
4211 intent) {
4212 var intentState = this.intentStates[intent];
4213 intentState.displayReadyPromise.resolve(transparency);
4214 },
4215 /**
4216 * For internal use only.
4217 * @ignore
4218 */
4219 _renderPageChunk: function PDFPageProxy_renderPageChunk(operatorListChunk,
4220 intent) {
4221 var intentState = this.intentStates[intent];
4222 var i, ii;
4223 // Add the new chunk to the current operator list.
4224 for (i = 0, ii = operatorListChunk.length; i < ii; i++) {
4225 intentState.operatorList.fnArray.push(operatorListChunk.fnArray[i]);
4226 intentState.operatorList.argsArray.push(
4227 operatorListChunk.argsArray[i]);
4228 }
4229 intentState.operatorList.lastChunk = operatorListChunk.lastChunk;
4231 // Notify all the rendering tasks there are more operators to be consumed.
4232 for (i = 0; i < intentState.renderTasks.length; i++) {
4233 intentState.renderTasks[i].operatorListChanged();
4234 }
4236 if (operatorListChunk.lastChunk) {
4237 intentState.receivingOperatorList = false;
4238 this._tryDestroy();
4239 }
4240 }
4241 };
4242 return PDFPageProxy;
4243 })();
4245 /**
4246 * For internal use only.
4247 * @ignore
4248 */
4249 var WorkerTransport = (function WorkerTransportClosure() {
4250 function WorkerTransport(workerInitializedPromise, workerReadyPromise,
4251 pdfDataRangeTransport, progressCallback) {
4252 this.pdfDataRangeTransport = pdfDataRangeTransport;
4254 this.workerReadyPromise = workerReadyPromise;
4255 this.progressCallback = progressCallback;
4256 this.commonObjs = new PDFObjects();
4258 this.pageCache = [];
4259 this.pagePromises = [];
4260 this.downloadInfoPromise = new PDFJS.LegacyPromise();
4261 this.passwordCallback = null;
4263 // If worker support isn't disabled explicit and the browser has worker
4264 // support, create a new web worker and test if it/the browser fullfills
4265 // all requirements to run parts of pdf.js in a web worker.
4266 // Right now, the requirement is, that an Uint8Array is still an Uint8Array
4267 // as it arrives on the worker. Chrome added this with version 15.
4268 if (!globalScope.PDFJS.disableWorker && typeof Worker !== 'undefined') {
4269 var workerSrc = PDFJS.workerSrc;
4270 if (!workerSrc) {
4271 error('No PDFJS.workerSrc specified');
4272 }
4274 try {
4275 // Some versions of FF can't create a worker on localhost, see:
4276 // https://bugzilla.mozilla.org/show_bug.cgi?id=683280
4277 var worker = new Worker(workerSrc);
4278 var messageHandler = new MessageHandler('main', worker);
4279 this.messageHandler = messageHandler;
4281 messageHandler.on('test', function transportTest(data) {
4282 var supportTypedArray = data && data.supportTypedArray;
4283 if (supportTypedArray) {
4284 this.worker = worker;
4285 if (!data.supportTransfers) {
4286 PDFJS.postMessageTransfers = false;
4287 }
4288 this.setupMessageHandler(messageHandler);
4289 workerInitializedPromise.resolve();
4290 } else {
4291 globalScope.PDFJS.disableWorker = true;
4292 this.loadFakeWorkerFiles().then(function() {
4293 this.setupFakeWorker();
4294 workerInitializedPromise.resolve();
4295 }.bind(this));
4296 }
4297 }.bind(this));
4299 var testObj = new Uint8Array([PDFJS.postMessageTransfers ? 255 : 0]);
4300 // Some versions of Opera throw a DATA_CLONE_ERR on serializing the
4301 // typed array. Also, checking if we can use transfers.
4302 try {
4303 messageHandler.send('test', testObj, null, [testObj.buffer]);
4304 } catch (ex) {
4305 info('Cannot use postMessage transfers');
4306 testObj[0] = 0;
4307 messageHandler.send('test', testObj);
4308 }
4309 return;
4310 } catch (e) {
4311 info('The worker has been disabled.');
4312 }
4313 }
4314 // Either workers are disabled, not supported or have thrown an exception.
4315 // Thus, we fallback to a faked worker.
4316 globalScope.PDFJS.disableWorker = true;
4317 this.loadFakeWorkerFiles().then(function() {
4318 this.setupFakeWorker();
4319 workerInitializedPromise.resolve();
4320 }.bind(this));
4321 }
4322 WorkerTransport.prototype = {
4323 destroy: function WorkerTransport_destroy() {
4324 this.pageCache = [];
4325 this.pagePromises = [];
4326 var self = this;
4327 this.messageHandler.send('Terminate', null, function () {
4328 FontLoader.clear();
4329 if (self.worker) {
4330 self.worker.terminate();
4331 }
4332 });
4333 },
4335 loadFakeWorkerFiles: function WorkerTransport_loadFakeWorkerFiles() {
4336 if (!PDFJS.fakeWorkerFilesLoadedPromise) {
4337 PDFJS.fakeWorkerFilesLoadedPromise = new LegacyPromise();
4338 // In the developer build load worker_loader which in turn loads all the
4339 // other files and resolves the promise. In production only the
4340 // pdf.worker.js file is needed.
4341 Util.loadScript(PDFJS.workerSrc, function() {
4342 PDFJS.fakeWorkerFilesLoadedPromise.resolve();
4343 });
4344 }
4345 return PDFJS.fakeWorkerFilesLoadedPromise;
4346 },
4348 setupFakeWorker: function WorkerTransport_setupFakeWorker() {
4349 warn('Setting up fake worker.');
4350 // If we don't use a worker, just post/sendMessage to the main thread.
4351 var fakeWorker = {
4352 postMessage: function WorkerTransport_postMessage(obj) {
4353 fakeWorker.onmessage({data: obj});
4354 },
4355 terminate: function WorkerTransport_terminate() {}
4356 };
4358 var messageHandler = new MessageHandler('main', fakeWorker);
4359 this.setupMessageHandler(messageHandler);
4361 // If the main thread is our worker, setup the handling for the messages
4362 // the main thread sends to it self.
4363 PDFJS.WorkerMessageHandler.setup(messageHandler);
4364 },
4366 setupMessageHandler:
4367 function WorkerTransport_setupMessageHandler(messageHandler) {
4368 this.messageHandler = messageHandler;
4370 function updatePassword(password) {
4371 messageHandler.send('UpdatePassword', password);
4372 }
4374 var pdfDataRangeTransport = this.pdfDataRangeTransport;
4375 if (pdfDataRangeTransport) {
4376 pdfDataRangeTransport.addRangeListener(function(begin, chunk) {
4377 messageHandler.send('OnDataRange', {
4378 begin: begin,
4379 chunk: chunk
4380 });
4381 });
4383 pdfDataRangeTransport.addProgressListener(function(loaded) {
4384 messageHandler.send('OnDataProgress', {
4385 loaded: loaded
4386 });
4387 });
4389 messageHandler.on('RequestDataRange',
4390 function transportDataRange(data) {
4391 pdfDataRangeTransport.requestDataRange(data.begin, data.end);
4392 }, this);
4393 }
4395 messageHandler.on('GetDoc', function transportDoc(data) {
4396 var pdfInfo = data.pdfInfo;
4397 this.numPages = data.pdfInfo.numPages;
4398 var pdfDocument = new PDFDocumentProxy(pdfInfo, this);
4399 this.pdfDocument = pdfDocument;
4400 this.workerReadyPromise.resolve(pdfDocument);
4401 }, this);
4403 messageHandler.on('NeedPassword', function transportPassword(data) {
4404 if (this.passwordCallback) {
4405 return this.passwordCallback(updatePassword,
4406 PasswordResponses.NEED_PASSWORD);
4407 }
4408 this.workerReadyPromise.reject(data.exception.message, data.exception);
4409 }, this);
4411 messageHandler.on('IncorrectPassword', function transportBadPass(data) {
4412 if (this.passwordCallback) {
4413 return this.passwordCallback(updatePassword,
4414 PasswordResponses.INCORRECT_PASSWORD);
4415 }
4416 this.workerReadyPromise.reject(data.exception.message, data.exception);
4417 }, this);
4419 messageHandler.on('InvalidPDF', function transportInvalidPDF(data) {
4420 this.workerReadyPromise.reject(data.exception.name, data.exception);
4421 }, this);
4423 messageHandler.on('MissingPDF', function transportMissingPDF(data) {
4424 this.workerReadyPromise.reject(data.exception.message, data.exception);
4425 }, this);
4427 messageHandler.on('UnknownError', function transportUnknownError(data) {
4428 this.workerReadyPromise.reject(data.exception.message, data.exception);
4429 }, this);
4431 messageHandler.on('DataLoaded', function transportPage(data) {
4432 this.downloadInfoPromise.resolve(data);
4433 }, this);
4435 messageHandler.on('GetPage', function transportPage(data) {
4436 var pageInfo = data.pageInfo;
4437 var page = new PDFPageProxy(pageInfo, this);
4438 this.pageCache[pageInfo.pageIndex] = page;
4439 var promise = this.pagePromises[pageInfo.pageIndex];
4440 promise.resolve(page);
4441 }, this);
4443 messageHandler.on('GetAnnotations', function transportAnnotations(data) {
4444 var annotations = data.annotations;
4445 var promise = this.pageCache[data.pageIndex].annotationsPromise;
4446 promise.resolve(annotations);
4447 }, this);
4449 messageHandler.on('StartRenderPage', function transportRender(data) {
4450 var page = this.pageCache[data.pageIndex];
4452 page.stats.timeEnd('Page Request');
4453 page._startRenderPage(data.transparency, data.intent);
4454 }, this);
4456 messageHandler.on('RenderPageChunk', function transportRender(data) {
4457 var page = this.pageCache[data.pageIndex];
4459 page._renderPageChunk(data.operatorList, data.intent);
4460 }, this);
4462 messageHandler.on('commonobj', function transportObj(data) {
4463 var id = data[0];
4464 var type = data[1];
4465 if (this.commonObjs.hasData(id)) {
4466 return;
4467 }
4469 switch (type) {
4470 case 'Font':
4471 var exportedData = data[2];
4473 var font;
4474 if ('error' in exportedData) {
4475 var error = exportedData.error;
4476 warn('Error during font loading: ' + error);
4477 this.commonObjs.resolve(id, error);
4478 break;
4479 } else {
4480 font = new FontFace(exportedData);
4481 }
4483 FontLoader.bind(
4484 [font],
4485 function fontReady(fontObjs) {
4486 this.commonObjs.resolve(id, font);
4487 }.bind(this)
4488 );
4489 break;
4490 case 'FontPath':
4491 this.commonObjs.resolve(id, data[2]);
4492 break;
4493 default:
4494 error('Got unknown common object type ' + type);
4495 }
4496 }, this);
4498 messageHandler.on('obj', function transportObj(data) {
4499 var id = data[0];
4500 var pageIndex = data[1];
4501 var type = data[2];
4502 var pageProxy = this.pageCache[pageIndex];
4503 var imageData;
4504 if (pageProxy.objs.hasData(id)) {
4505 return;
4506 }
4508 switch (type) {
4509 case 'JpegStream':
4510 imageData = data[3];
4511 loadJpegStream(id, imageData, pageProxy.objs);
4512 break;
4513 case 'Image':
4514 imageData = data[3];
4515 pageProxy.objs.resolve(id, imageData);
4517 // heuristics that will allow not to store large data
4518 var MAX_IMAGE_SIZE_TO_STORE = 8000000;
4519 if (imageData && 'data' in imageData &&
4520 imageData.data.length > MAX_IMAGE_SIZE_TO_STORE) {
4521 pageProxy.cleanupAfterRender = true;
4522 }
4523 break;
4524 default:
4525 error('Got unknown object type ' + type);
4526 }
4527 }, this);
4529 messageHandler.on('DocProgress', function transportDocProgress(data) {
4530 if (this.progressCallback) {
4531 this.progressCallback({
4532 loaded: data.loaded,
4533 total: data.total
4534 });
4535 }
4536 }, this);
4538 messageHandler.on('DocError', function transportDocError(data) {
4539 this.workerReadyPromise.reject(data);
4540 }, this);
4542 messageHandler.on('PageError', function transportError(data, intent) {
4543 var page = this.pageCache[data.pageNum - 1];
4544 var intentState = page.intentStates[intent];
4545 if (intentState.displayReadyPromise) {
4546 intentState.displayReadyPromise.reject(data.error);
4547 } else {
4548 error(data.error);
4549 }
4550 }, this);
4552 messageHandler.on('JpegDecode', function(data, deferred) {
4553 var imageUrl = data[0];
4554 var components = data[1];
4555 if (components != 3 && components != 1) {
4556 error('Only 3 component or 1 component can be returned');
4557 }
4559 var img = new Image();
4560 img.onload = (function messageHandler_onloadClosure() {
4561 var width = img.width;
4562 var height = img.height;
4563 var size = width * height;
4564 var rgbaLength = size * 4;
4565 var buf = new Uint8Array(size * components);
4566 var tmpCanvas = createScratchCanvas(width, height);
4567 var tmpCtx = tmpCanvas.getContext('2d');
4568 tmpCtx.drawImage(img, 0, 0);
4569 var data = tmpCtx.getImageData(0, 0, width, height).data;
4570 var i, j;
4572 if (components == 3) {
4573 for (i = 0, j = 0; i < rgbaLength; i += 4, j += 3) {
4574 buf[j] = data[i];
4575 buf[j + 1] = data[i + 1];
4576 buf[j + 2] = data[i + 2];
4577 }
4578 } else if (components == 1) {
4579 for (i = 0, j = 0; i < rgbaLength; i += 4, j++) {
4580 buf[j] = data[i];
4581 }
4582 }
4583 deferred.resolve({ data: buf, width: width, height: height});
4584 }).bind(this);
4585 img.src = imageUrl;
4586 });
4587 },
4589 fetchDocument: function WorkerTransport_fetchDocument(source) {
4590 source.disableAutoFetch = PDFJS.disableAutoFetch;
4591 source.chunkedViewerLoading = !!this.pdfDataRangeTransport;
4592 this.messageHandler.send('GetDocRequest', {
4593 source: source,
4594 disableRange: PDFJS.disableRange,
4595 maxImageSize: PDFJS.maxImageSize,
4596 cMapUrl: PDFJS.cMapUrl,
4597 cMapPacked: PDFJS.cMapPacked,
4598 disableFontFace: PDFJS.disableFontFace,
4599 disableCreateObjectURL: PDFJS.disableCreateObjectURL,
4600 verbosity: PDFJS.verbosity
4601 });
4602 },
4604 getData: function WorkerTransport_getData(promise) {
4605 this.messageHandler.send('GetData', null, function(data) {
4606 promise.resolve(data);
4607 });
4608 },
4610 getPage: function WorkerTransport_getPage(pageNumber, promise) {
4611 if (pageNumber <= 0 || pageNumber > this.numPages ||
4612 (pageNumber|0) !== pageNumber) {
4613 var pagePromise = new PDFJS.LegacyPromise();
4614 pagePromise.reject(new Error('Invalid page request'));
4615 return pagePromise;
4616 }
4618 var pageIndex = pageNumber - 1;
4619 if (pageIndex in this.pagePromises) {
4620 return this.pagePromises[pageIndex];
4621 }
4622 promise = new PDFJS.LegacyPromise();
4623 this.pagePromises[pageIndex] = promise;
4624 this.messageHandler.send('GetPageRequest', { pageIndex: pageIndex });
4625 return promise;
4626 },
4628 getPageIndex: function WorkerTransport_getPageIndexByRef(ref) {
4629 var promise = new PDFJS.LegacyPromise();
4630 this.messageHandler.send('GetPageIndex', { ref: ref },
4631 function (pageIndex) {
4632 promise.resolve(pageIndex);
4633 }
4634 );
4635 return promise;
4636 },
4638 getAnnotations: function WorkerTransport_getAnnotations(pageIndex) {
4639 this.messageHandler.send('GetAnnotationsRequest',
4640 { pageIndex: pageIndex });
4641 },
4643 getDestinations: function WorkerTransport_getDestinations() {
4644 var promise = new PDFJS.LegacyPromise();
4645 this.messageHandler.send('GetDestinations', null,
4646 function transportDestinations(destinations) {
4647 promise.resolve(destinations);
4648 }
4649 );
4650 return promise;
4651 },
4653 getAttachments: function WorkerTransport_getAttachments() {
4654 var promise = new PDFJS.LegacyPromise();
4655 this.messageHandler.send('GetAttachments', null,
4656 function transportAttachments(attachments) {
4657 promise.resolve(attachments);
4658 }
4659 );
4660 return promise;
4661 },
4663 startCleanup: function WorkerTransport_startCleanup() {
4664 this.messageHandler.send('Cleanup', null,
4665 function endCleanup() {
4666 for (var i = 0, ii = this.pageCache.length; i < ii; i++) {
4667 var page = this.pageCache[i];
4668 if (page) {
4669 page.destroy();
4670 }
4671 }
4672 this.commonObjs.clear();
4673 FontLoader.clear();
4674 }.bind(this)
4675 );
4676 }
4677 };
4678 return WorkerTransport;
4680 })();
4682 /**
4683 * A PDF document and page is built of many objects. E.g. there are objects
4684 * for fonts, images, rendering code and such. These objects might get processed
4685 * inside of a worker. The `PDFObjects` implements some basic functions to
4686 * manage these objects.
4687 * @ignore
4688 */
4689 var PDFObjects = (function PDFObjectsClosure() {
4690 function PDFObjects() {
4691 this.objs = {};
4692 }
4694 PDFObjects.prototype = {
4695 /**
4696 * Internal function.
4697 * Ensures there is an object defined for `objId`.
4698 */
4699 ensureObj: function PDFObjects_ensureObj(objId) {
4700 if (this.objs[objId]) {
4701 return this.objs[objId];
4702 }
4704 var obj = {
4705 promise: new LegacyPromise(),
4706 data: null,
4707 resolved: false
4708 };
4709 this.objs[objId] = obj;
4711 return obj;
4712 },
4714 /**
4715 * If called *without* callback, this returns the data of `objId` but the
4716 * object needs to be resolved. If it isn't, this function throws.
4717 *
4718 * If called *with* a callback, the callback is called with the data of the
4719 * object once the object is resolved. That means, if you call this
4720 * function and the object is already resolved, the callback gets called
4721 * right away.
4722 */
4723 get: function PDFObjects_get(objId, callback) {
4724 // If there is a callback, then the get can be async and the object is
4725 // not required to be resolved right now
4726 if (callback) {
4727 this.ensureObj(objId).promise.then(callback);
4728 return null;
4729 }
4731 // If there isn't a callback, the user expects to get the resolved data
4732 // directly.
4733 var obj = this.objs[objId];
4735 // If there isn't an object yet or the object isn't resolved, then the
4736 // data isn't ready yet!
4737 if (!obj || !obj.resolved) {
4738 error('Requesting object that isn\'t resolved yet ' + objId);
4739 }
4741 return obj.data;
4742 },
4744 /**
4745 * Resolves the object `objId` with optional `data`.
4746 */
4747 resolve: function PDFObjects_resolve(objId, data) {
4748 var obj = this.ensureObj(objId);
4750 obj.resolved = true;
4751 obj.data = data;
4752 obj.promise.resolve(data);
4753 },
4755 isResolved: function PDFObjects_isResolved(objId) {
4756 var objs = this.objs;
4758 if (!objs[objId]) {
4759 return false;
4760 } else {
4761 return objs[objId].resolved;
4762 }
4763 },
4765 hasData: function PDFObjects_hasData(objId) {
4766 return this.isResolved(objId);
4767 },
4769 /**
4770 * Returns the data of `objId` if object exists, null otherwise.
4771 */
4772 getData: function PDFObjects_getData(objId) {
4773 var objs = this.objs;
4774 if (!objs[objId] || !objs[objId].resolved) {
4775 return null;
4776 } else {
4777 return objs[objId].data;
4778 }
4779 },
4781 clear: function PDFObjects_clear() {
4782 this.objs = {};
4783 }
4784 };
4785 return PDFObjects;
4786 })();
4788 /**
4789 * Allows controlling of the rendering tasks.
4790 * @class
4791 */
4792 var RenderTask = (function RenderTaskClosure() {
4793 function RenderTask(internalRenderTask) {
4794 this.internalRenderTask = internalRenderTask;
4795 /**
4796 * Promise for rendering task completion.
4797 * @type {Promise}
4798 */
4799 this.promise = new PDFJS.LegacyPromise();
4800 }
4802 RenderTask.prototype = /** @lends RenderTask.prototype */ {
4803 /**
4804 * Cancels the rendering task. If the task is currently rendering it will
4805 * not be cancelled until graphics pauses with a timeout. The promise that
4806 * this object extends will resolved when cancelled.
4807 */
4808 cancel: function RenderTask_cancel() {
4809 this.internalRenderTask.cancel();
4810 this.promise.reject(new Error('Rendering is cancelled'));
4811 },
4813 /**
4814 * Registers callback to indicate the rendering task completion.
4815 *
4816 * @param {function} onFulfilled The callback for the rendering completion.
4817 * @param {function} onRejected The callback for the rendering failure.
4818 * @return {Promise} A promise that is resolved after the onFulfilled or
4819 * onRejected callback.
4820 */
4821 then: function RenderTask_then(onFulfilled, onRejected) {
4822 return this.promise.then(onFulfilled, onRejected);
4823 }
4824 };
4826 return RenderTask;
4827 })();
4829 /**
4830 * For internal use only.
4831 * @ignore
4832 */
4833 var InternalRenderTask = (function InternalRenderTaskClosure() {
4835 function InternalRenderTask(callback, params, objs, commonObjs, operatorList,
4836 pageNumber) {
4837 this.callback = callback;
4838 this.params = params;
4839 this.objs = objs;
4840 this.commonObjs = commonObjs;
4841 this.operatorListIdx = null;
4842 this.operatorList = operatorList;
4843 this.pageNumber = pageNumber;
4844 this.running = false;
4845 this.graphicsReadyCallback = null;
4846 this.graphicsReady = false;
4847 this.cancelled = false;
4848 }
4850 InternalRenderTask.prototype = {
4852 initalizeGraphics:
4853 function InternalRenderTask_initalizeGraphics(transparency) {
4855 if (this.cancelled) {
4856 return;
4857 }
4858 if (PDFJS.pdfBug && 'StepperManager' in globalScope &&
4859 globalScope.StepperManager.enabled) {
4860 this.stepper = globalScope.StepperManager.create(this.pageNumber - 1);
4861 this.stepper.init(this.operatorList);
4862 this.stepper.nextBreakPoint = this.stepper.getNextBreakPoint();
4863 }
4865 var params = this.params;
4866 this.gfx = new CanvasGraphics(params.canvasContext, this.commonObjs,
4867 this.objs, params.imageLayer);
4869 this.gfx.beginDrawing(params.viewport, transparency);
4870 this.operatorListIdx = 0;
4871 this.graphicsReady = true;
4872 if (this.graphicsReadyCallback) {
4873 this.graphicsReadyCallback();
4874 }
4875 },
4877 cancel: function InternalRenderTask_cancel() {
4878 this.running = false;
4879 this.cancelled = true;
4880 this.callback('cancelled');
4881 },
4883 operatorListChanged: function InternalRenderTask_operatorListChanged() {
4884 if (!this.graphicsReady) {
4885 if (!this.graphicsReadyCallback) {
4886 this.graphicsReadyCallback = this._continue.bind(this);
4887 }
4888 return;
4889 }
4891 if (this.stepper) {
4892 this.stepper.updateOperatorList(this.operatorList);
4893 }
4895 if (this.running) {
4896 return;
4897 }
4898 this._continue();
4899 },
4901 _continue: function InternalRenderTask__continue() {
4902 this.running = true;
4903 if (this.cancelled) {
4904 return;
4905 }
4906 if (this.params.continueCallback) {
4907 this.params.continueCallback(this._next.bind(this));
4908 } else {
4909 this._next();
4910 }
4911 },
4913 _next: function InternalRenderTask__next() {
4914 if (this.cancelled) {
4915 return;
4916 }
4917 this.operatorListIdx = this.gfx.executeOperatorList(this.operatorList,
4918 this.operatorListIdx,
4919 this._continue.bind(this),
4920 this.stepper);
4921 if (this.operatorListIdx === this.operatorList.argsArray.length) {
4922 this.running = false;
4923 if (this.operatorList.lastChunk) {
4924 this.gfx.endDrawing();
4925 this.callback();
4926 }
4927 }
4928 }
4930 };
4932 return InternalRenderTask;
4933 })();
4936 var Metadata = PDFJS.Metadata = (function MetadataClosure() {
4937 function fixMetadata(meta) {
4938 return meta.replace(/>\\376\\377([^<]+)/g, function(all, codes) {
4939 var bytes = codes.replace(/\\([0-3])([0-7])([0-7])/g,
4940 function(code, d1, d2, d3) {
4941 return String.fromCharCode(d1 * 64 + d2 * 8 + d3 * 1);
4942 });
4943 var chars = '';
4944 for (var i = 0; i < bytes.length; i += 2) {
4945 var code = bytes.charCodeAt(i) * 256 + bytes.charCodeAt(i + 1);
4946 chars += code >= 32 && code < 127 && code != 60 && code != 62 &&
4947 code != 38 && false ? String.fromCharCode(code) :
4948 '&#x' + (0x10000 + code).toString(16).substring(1) + ';';
4949 }
4950 return '>' + chars;
4951 });
4952 }
4954 function Metadata(meta) {
4955 if (typeof meta === 'string') {
4956 // Ghostscript produces invalid metadata
4957 meta = fixMetadata(meta);
4959 var parser = new DOMParser();
4960 meta = parser.parseFromString(meta, 'application/xml');
4961 } else if (!(meta instanceof Document)) {
4962 error('Metadata: Invalid metadata object');
4963 }
4965 this.metaDocument = meta;
4966 this.metadata = {};
4967 this.parse();
4968 }
4970 Metadata.prototype = {
4971 parse: function Metadata_parse() {
4972 var doc = this.metaDocument;
4973 var rdf = doc.documentElement;
4975 if (rdf.nodeName.toLowerCase() !== 'rdf:rdf') { // Wrapped in <xmpmeta>
4976 rdf = rdf.firstChild;
4977 while (rdf && rdf.nodeName.toLowerCase() !== 'rdf:rdf') {
4978 rdf = rdf.nextSibling;
4979 }
4980 }
4982 var nodeName = (rdf) ? rdf.nodeName.toLowerCase() : null;
4983 if (!rdf || nodeName !== 'rdf:rdf' || !rdf.hasChildNodes()) {
4984 return;
4985 }
4987 var children = rdf.childNodes, desc, entry, name, i, ii, length, iLength;
4988 for (i = 0, length = children.length; i < length; i++) {
4989 desc = children[i];
4990 if (desc.nodeName.toLowerCase() !== 'rdf:description') {
4991 continue;
4992 }
4994 for (ii = 0, iLength = desc.childNodes.length; ii < iLength; ii++) {
4995 if (desc.childNodes[ii].nodeName.toLowerCase() !== '#text') {
4996 entry = desc.childNodes[ii];
4997 name = entry.nodeName.toLowerCase();
4998 this.metadata[name] = entry.textContent.trim();
4999 }
5000 }
5001 }
5002 },
5004 get: function Metadata_get(name) {
5005 return this.metadata[name] || null;
5006 },
5008 has: function Metadata_has(name) {
5009 return typeof this.metadata[name] !== 'undefined';
5010 }
5011 };
5013 return Metadata;
5014 })();
5017 // <canvas> contexts store most of the state we need natively.
5018 // However, PDF needs a bit more state, which we store here.
5020 // Minimal font size that would be used during canvas fillText operations.
5021 var MIN_FONT_SIZE = 16;
5022 var MAX_GROUP_SIZE = 4096;
5024 var COMPILE_TYPE3_GLYPHS = true;
5026 function createScratchCanvas(width, height) {
5027 var canvas = document.createElement('canvas');
5028 canvas.width = width;
5029 canvas.height = height;
5030 return canvas;
5031 }
5033 function addContextCurrentTransform(ctx) {
5034 // If the context doesn't expose a `mozCurrentTransform`, add a JS based on.
5035 if (!ctx.mozCurrentTransform) {
5036 // Store the original context
5037 ctx._scaleX = ctx._scaleX || 1.0;
5038 ctx._scaleY = ctx._scaleY || 1.0;
5039 ctx._originalSave = ctx.save;
5040 ctx._originalRestore = ctx.restore;
5041 ctx._originalRotate = ctx.rotate;
5042 ctx._originalScale = ctx.scale;
5043 ctx._originalTranslate = ctx.translate;
5044 ctx._originalTransform = ctx.transform;
5045 ctx._originalSetTransform = ctx.setTransform;
5047 ctx._transformMatrix = [ctx._scaleX, 0, 0, ctx._scaleY, 0, 0];
5048 ctx._transformStack = [];
5050 Object.defineProperty(ctx, 'mozCurrentTransform', {
5051 get: function getCurrentTransform() {
5052 return this._transformMatrix;
5053 }
5054 });
5056 Object.defineProperty(ctx, 'mozCurrentTransformInverse', {
5057 get: function getCurrentTransformInverse() {
5058 // Calculation done using WolframAlpha:
5059 // http://www.wolframalpha.com/input/?
5060 // i=Inverse+{{a%2C+c%2C+e}%2C+{b%2C+d%2C+f}%2C+{0%2C+0%2C+1}}
5062 var m = this._transformMatrix;
5063 var a = m[0], b = m[1], c = m[2], d = m[3], e = m[4], f = m[5];
5065 var ad_bc = a * d - b * c;
5066 var bc_ad = b * c - a * d;
5068 return [
5069 d / ad_bc,
5070 b / bc_ad,
5071 c / bc_ad,
5072 a / ad_bc,
5073 (d * e - c * f) / bc_ad,
5074 (b * e - a * f) / ad_bc
5075 ];
5076 }
5077 });
5079 ctx.save = function ctxSave() {
5080 var old = this._transformMatrix;
5081 this._transformStack.push(old);
5082 this._transformMatrix = old.slice(0, 6);
5084 this._originalSave();
5085 };
5087 ctx.restore = function ctxRestore() {
5088 var prev = this._transformStack.pop();
5089 if (prev) {
5090 this._transformMatrix = prev;
5091 this._originalRestore();
5092 }
5093 };
5095 ctx.translate = function ctxTranslate(x, y) {
5096 var m = this._transformMatrix;
5097 m[4] = m[0] * x + m[2] * y + m[4];
5098 m[5] = m[1] * x + m[3] * y + m[5];
5100 this._originalTranslate(x, y);
5101 };
5103 ctx.scale = function ctxScale(x, y) {
5104 var m = this._transformMatrix;
5105 m[0] = m[0] * x;
5106 m[1] = m[1] * x;
5107 m[2] = m[2] * y;
5108 m[3] = m[3] * y;
5110 this._originalScale(x, y);
5111 };
5113 ctx.transform = function ctxTransform(a, b, c, d, e, f) {
5114 var m = this._transformMatrix;
5115 this._transformMatrix = [
5116 m[0] * a + m[2] * b,
5117 m[1] * a + m[3] * b,
5118 m[0] * c + m[2] * d,
5119 m[1] * c + m[3] * d,
5120 m[0] * e + m[2] * f + m[4],
5121 m[1] * e + m[3] * f + m[5]
5122 ];
5124 ctx._originalTransform(a, b, c, d, e, f);
5125 };
5127 ctx.setTransform = function ctxSetTransform(a, b, c, d, e, f) {
5128 this._transformMatrix = [a, b, c, d, e, f];
5130 ctx._originalSetTransform(a, b, c, d, e, f);
5131 };
5133 ctx.rotate = function ctxRotate(angle) {
5134 var cosValue = Math.cos(angle);
5135 var sinValue = Math.sin(angle);
5137 var m = this._transformMatrix;
5138 this._transformMatrix = [
5139 m[0] * cosValue + m[2] * sinValue,
5140 m[1] * cosValue + m[3] * sinValue,
5141 m[0] * (-sinValue) + m[2] * cosValue,
5142 m[1] * (-sinValue) + m[3] * cosValue,
5143 m[4],
5144 m[5]
5145 ];
5147 this._originalRotate(angle);
5148 };
5149 }
5150 }
5152 var CachedCanvases = (function CachedCanvasesClosure() {
5153 var cache = {};
5154 return {
5155 getCanvas: function CachedCanvases_getCanvas(id, width, height,
5156 trackTransform) {
5157 var canvasEntry;
5158 if (id in cache) {
5159 canvasEntry = cache[id];
5160 canvasEntry.canvas.width = width;
5161 canvasEntry.canvas.height = height;
5162 // reset canvas transform for emulated mozCurrentTransform, if needed
5163 canvasEntry.context.setTransform(1, 0, 0, 1, 0, 0);
5164 } else {
5165 var canvas = createScratchCanvas(width, height);
5166 var ctx = canvas.getContext('2d');
5167 if (trackTransform) {
5168 addContextCurrentTransform(ctx);
5169 }
5170 cache[id] = canvasEntry = {canvas: canvas, context: ctx};
5171 }
5172 return canvasEntry;
5173 },
5174 clear: function () {
5175 cache = {};
5176 }
5177 };
5178 })();
5180 function compileType3Glyph(imgData) {
5181 var POINT_TO_PROCESS_LIMIT = 1000;
5183 var width = imgData.width, height = imgData.height;
5184 var i, j, j0, width1 = width + 1;
5185 var points = new Uint8Array(width1 * (height + 1));
5186 var POINT_TYPES =
5187 new Uint8Array([0, 2, 4, 0, 1, 0, 5, 4, 8, 10, 0, 8, 0, 2, 1, 0]);
5189 // decodes bit-packed mask data
5190 var lineSize = (width + 7) & ~7, data0 = imgData.data;
5191 var data = new Uint8Array(lineSize * height), pos = 0, ii;
5192 for (i = 0, ii = data0.length; i < ii; i++) {
5193 var mask = 128, elem = data0[i];
5194 while (mask > 0) {
5195 data[pos++] = (elem & mask) ? 0 : 255;
5196 mask >>= 1;
5197 }
5198 }
5200 // finding iteresting points: every point is located between mask pixels,
5201 // so there will be points of the (width + 1)x(height + 1) grid. Every point
5202 // will have flags assigned based on neighboring mask pixels:
5203 // 4 | 8
5204 // --P--
5205 // 2 | 1
5206 // We are interested only in points with the flags:
5207 // - outside corners: 1, 2, 4, 8;
5208 // - inside corners: 7, 11, 13, 14;
5209 // - and, intersections: 5, 10.
5210 var count = 0;
5211 pos = 0;
5212 if (data[pos] !== 0) {
5213 points[0] = 1;
5214 ++count;
5215 }
5216 for (j = 1; j < width; j++) {
5217 if (data[pos] !== data[pos + 1]) {
5218 points[j] = data[pos] ? 2 : 1;
5219 ++count;
5220 }
5221 pos++;
5222 }
5223 if (data[pos] !== 0) {
5224 points[j] = 2;
5225 ++count;
5226 }
5227 for (i = 1; i < height; i++) {
5228 pos = i * lineSize;
5229 j0 = i * width1;
5230 if (data[pos - lineSize] !== data[pos]) {
5231 points[j0] = data[pos] ? 1 : 8;
5232 ++count;
5233 }
5234 // 'sum' is the position of the current pixel configuration in the 'TYPES'
5235 // array (in order 8-1-2-4, so we can use '>>2' to shift the column).
5236 var sum = (data[pos] ? 4 : 0) + (data[pos - lineSize] ? 8 : 0);
5237 for (j = 1; j < width; j++) {
5238 sum = (sum >> 2) + (data[pos + 1] ? 4 : 0) +
5239 (data[pos - lineSize + 1] ? 8 : 0);
5240 if (POINT_TYPES[sum]) {
5241 points[j0 + j] = POINT_TYPES[sum];
5242 ++count;
5243 }
5244 pos++;
5245 }
5246 if (data[pos - lineSize] !== data[pos]) {
5247 points[j0 + j] = data[pos] ? 2 : 4;
5248 ++count;
5249 }
5251 if (count > POINT_TO_PROCESS_LIMIT) {
5252 return null;
5253 }
5254 }
5256 pos = lineSize * (height - 1);
5257 j0 = i * width1;
5258 if (data[pos] !== 0) {
5259 points[j0] = 8;
5260 ++count;
5261 }
5262 for (j = 1; j < width; j++) {
5263 if (data[pos] !== data[pos + 1]) {
5264 points[j0 + j] = data[pos] ? 4 : 8;
5265 ++count;
5266 }
5267 pos++;
5268 }
5269 if (data[pos] !== 0) {
5270 points[j0 + j] = 4;
5271 ++count;
5272 }
5273 if (count > POINT_TO_PROCESS_LIMIT) {
5274 return null;
5275 }
5277 // building outlines
5278 var steps = new Int32Array([0, width1, -1, 0, -width1, 0, 0, 0, 1]);
5279 var outlines = [];
5280 for (i = 0; count && i <= height; i++) {
5281 var p = i * width1;
5282 var end = p + width;
5283 while (p < end && !points[p]) {
5284 p++;
5285 }
5286 if (p === end) {
5287 continue;
5288 }
5289 var coords = [p % width1, i];
5291 var type = points[p], p0 = p, pp;
5292 do {
5293 var step = steps[type];
5294 do {
5295 p += step;
5296 } while (!points[p]);
5298 pp = points[p];
5299 if (pp !== 5 && pp !== 10) {
5300 // set new direction
5301 type = pp;
5302 // delete mark
5303 points[p] = 0;
5304 } else { // type is 5 or 10, ie, a crossing
5305 // set new direction
5306 type = pp & ((0x33 * type) >> 4);
5307 // set new type for "future hit"
5308 points[p] &= (type >> 2 | type << 2);
5309 }
5311 coords.push(p % width1);
5312 coords.push((p / width1) | 0);
5313 --count;
5314 } while (p0 !== p);
5315 outlines.push(coords);
5316 --i;
5317 }
5319 var drawOutline = function(c) {
5320 c.save();
5321 // the path shall be painted in [0..1]x[0..1] space
5322 c.scale(1 / width, -1 / height);
5323 c.translate(0, -height);
5324 c.beginPath();
5325 for (var i = 0, ii = outlines.length; i < ii; i++) {
5326 var o = outlines[i];
5327 c.moveTo(o[0], o[1]);
5328 for (var j = 2, jj = o.length; j < jj; j += 2) {
5329 c.lineTo(o[j], o[j+1]);
5330 }
5331 }
5332 c.fill();
5333 c.beginPath();
5334 c.restore();
5335 };
5337 return drawOutline;
5338 }
5340 var CanvasExtraState = (function CanvasExtraStateClosure() {
5341 function CanvasExtraState(old) {
5342 // Are soft masks and alpha values shapes or opacities?
5343 this.alphaIsShape = false;
5344 this.fontSize = 0;
5345 this.fontSizeScale = 1;
5346 this.textMatrix = IDENTITY_MATRIX;
5347 this.fontMatrix = FONT_IDENTITY_MATRIX;
5348 this.leading = 0;
5349 // Current point (in user coordinates)
5350 this.x = 0;
5351 this.y = 0;
5352 // Start of text line (in text coordinates)
5353 this.lineX = 0;
5354 this.lineY = 0;
5355 // Character and word spacing
5356 this.charSpacing = 0;
5357 this.wordSpacing = 0;
5358 this.textHScale = 1;
5359 this.textRenderingMode = TextRenderingMode.FILL;
5360 this.textRise = 0;
5361 // Color spaces
5362 this.fillColorSpace = ColorSpace.singletons.gray;
5363 this.fillColorSpaceObj = null;
5364 this.strokeColorSpace = ColorSpace.singletons.gray;
5365 this.strokeColorSpaceObj = null;
5366 this.fillColorObj = null;
5367 this.strokeColorObj = null;
5368 // Default fore and background colors
5369 this.fillColor = '#000000';
5370 this.strokeColor = '#000000';
5371 // Note: fill alpha applies to all non-stroking operations
5372 this.fillAlpha = 1;
5373 this.strokeAlpha = 1;
5374 this.lineWidth = 1;
5375 this.activeSMask = null; // nonclonable field (see the save method below)
5377 this.old = old;
5378 }
5380 CanvasExtraState.prototype = {
5381 clone: function CanvasExtraState_clone() {
5382 return Object.create(this);
5383 },
5384 setCurrentPoint: function CanvasExtraState_setCurrentPoint(x, y) {
5385 this.x = x;
5386 this.y = y;
5387 }
5388 };
5389 return CanvasExtraState;
5390 })();
5392 var CanvasGraphics = (function CanvasGraphicsClosure() {
5393 // Defines the time the executeOperatorList is going to be executing
5394 // before it stops and shedules a continue of execution.
5395 var EXECUTION_TIME = 15;
5397 function CanvasGraphics(canvasCtx, commonObjs, objs, imageLayer) {
5398 this.ctx = canvasCtx;
5399 this.current = new CanvasExtraState();
5400 this.stateStack = [];
5401 this.pendingClip = null;
5402 this.pendingEOFill = false;
5403 this.res = null;
5404 this.xobjs = null;
5405 this.commonObjs = commonObjs;
5406 this.objs = objs;
5407 this.imageLayer = imageLayer;
5408 this.groupStack = [];
5409 this.processingType3 = null;
5410 // Patterns are painted relative to the initial page/form transform, see pdf
5411 // spec 8.7.2 NOTE 1.
5412 this.baseTransform = null;
5413 this.baseTransformStack = [];
5414 this.groupLevel = 0;
5415 this.smaskStack = [];
5416 this.smaskCounter = 0;
5417 this.tempSMask = null;
5418 if (canvasCtx) {
5419 addContextCurrentTransform(canvasCtx);
5420 }
5421 }
5423 function putBinaryImageData(ctx, imgData) {
5424 if (typeof ImageData !== 'undefined' && imgData instanceof ImageData) {
5425 ctx.putImageData(imgData, 0, 0);
5426 return;
5427 }
5429 // Put the image data to the canvas in chunks, rather than putting the
5430 // whole image at once. This saves JS memory, because the ImageData object
5431 // is smaller. It also possibly saves C++ memory within the implementation
5432 // of putImageData(). (E.g. in Firefox we make two short-lived copies of
5433 // the data passed to putImageData()). |n| shouldn't be too small, however,
5434 // because too many putImageData() calls will slow things down.
5435 //
5436 // Note: as written, if the last chunk is partial, the putImageData() call
5437 // will (conceptually) put pixels past the bounds of the canvas. But
5438 // that's ok; any such pixels are ignored.
5440 var height = imgData.height, width = imgData.width;
5441 var fullChunkHeight = 16;
5442 var fracChunks = height / fullChunkHeight;
5443 var fullChunks = Math.floor(fracChunks);
5444 var totalChunks = Math.ceil(fracChunks);
5445 var partialChunkHeight = height - fullChunks * fullChunkHeight;
5447 var chunkImgData = ctx.createImageData(width, fullChunkHeight);
5448 var srcPos = 0, destPos;
5449 var src = imgData.data;
5450 var dest = chunkImgData.data;
5451 var i, j, thisChunkHeight, elemsInThisChunk;
5453 // There are multiple forms in which the pixel data can be passed, and
5454 // imgData.kind tells us which one this is.
5455 if (imgData.kind === ImageKind.GRAYSCALE_1BPP) {
5456 // Grayscale, 1 bit per pixel (i.e. black-and-white).
5457 var srcLength = src.byteLength;
5458 var dest32 = PDFJS.hasCanvasTypedArrays ? new Uint32Array(dest.buffer) :
5459 new Uint32ArrayView(dest);
5460 var dest32DataLength = dest32.length;
5461 var fullSrcDiff = (width + 7) >> 3;
5462 var white = 0xFFFFFFFF;
5463 var black = (PDFJS.isLittleEndian || !PDFJS.hasCanvasTypedArrays) ?
5464 0xFF000000 : 0x000000FF;
5465 for (i = 0; i < totalChunks; i++) {
5466 thisChunkHeight =
5467 (i < fullChunks) ? fullChunkHeight : partialChunkHeight;
5468 destPos = 0;
5469 for (j = 0; j < thisChunkHeight; j++) {
5470 var srcDiff = srcLength - srcPos;
5471 var k = 0;
5472 var kEnd = (srcDiff > fullSrcDiff) ? width : srcDiff * 8 - 7;
5473 var kEndUnrolled = kEnd & ~7;
5474 var mask = 0;
5475 var srcByte = 0;
5476 for (; k < kEndUnrolled; k += 8) {
5477 srcByte = src[srcPos++];
5478 dest32[destPos++] = (srcByte & 128) ? white : black;
5479 dest32[destPos++] = (srcByte & 64) ? white : black;
5480 dest32[destPos++] = (srcByte & 32) ? white : black;
5481 dest32[destPos++] = (srcByte & 16) ? white : black;
5482 dest32[destPos++] = (srcByte & 8) ? white : black;
5483 dest32[destPos++] = (srcByte & 4) ? white : black;
5484 dest32[destPos++] = (srcByte & 2) ? white : black;
5485 dest32[destPos++] = (srcByte & 1) ? white : black;
5486 }
5487 for (; k < kEnd; k++) {
5488 if (mask === 0) {
5489 srcByte = src[srcPos++];
5490 mask = 128;
5491 }
5493 dest32[destPos++] = (srcByte & mask) ? white : black;
5494 mask >>= 1;
5495 }
5496 }
5497 // We ran out of input. Make all remaining pixels transparent.
5498 while (destPos < dest32DataLength) {
5499 dest32[destPos++] = 0;
5500 }
5502 ctx.putImageData(chunkImgData, 0, i * fullChunkHeight);
5503 }
5504 } else if (imgData.kind === ImageKind.RGBA_32BPP) {
5505 // RGBA, 32-bits per pixel.
5507 for (i = 0; i < totalChunks; i++) {
5508 thisChunkHeight =
5509 (i < fullChunks) ? fullChunkHeight : partialChunkHeight;
5510 elemsInThisChunk = imgData.width * thisChunkHeight * 4;
5512 dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
5513 srcPos += elemsInThisChunk;
5515 ctx.putImageData(chunkImgData, 0, i * fullChunkHeight);
5516 }
5517 } else if (imgData.kind === ImageKind.RGB_24BPP) {
5518 // RGB, 24-bits per pixel.
5519 for (i = 0; i < totalChunks; i++) {
5520 thisChunkHeight =
5521 (i < fullChunks) ? fullChunkHeight : partialChunkHeight;
5522 elemsInThisChunk = imgData.width * thisChunkHeight * 3;
5523 destPos = 0;
5524 for (j = 0; j < elemsInThisChunk; j += 3) {
5525 dest[destPos++] = src[srcPos++];
5526 dest[destPos++] = src[srcPos++];
5527 dest[destPos++] = src[srcPos++];
5528 dest[destPos++] = 255;
5529 }
5530 ctx.putImageData(chunkImgData, 0, i * fullChunkHeight);
5531 }
5532 } else {
5533 error('bad image kind: ' + imgData.kind);
5534 }
5535 }
5537 function putBinaryImageMask(ctx, imgData) {
5538 var height = imgData.height, width = imgData.width;
5539 var fullChunkHeight = 16;
5540 var fracChunks = height / fullChunkHeight;
5541 var fullChunks = Math.floor(fracChunks);
5542 var totalChunks = Math.ceil(fracChunks);
5543 var partialChunkHeight = height - fullChunks * fullChunkHeight;
5545 var chunkImgData = ctx.createImageData(width, fullChunkHeight);
5546 var srcPos = 0;
5547 var src = imgData.data;
5548 var dest = chunkImgData.data;
5550 for (var i = 0; i < totalChunks; i++) {
5551 var thisChunkHeight =
5552 (i < fullChunks) ? fullChunkHeight : partialChunkHeight;
5554 // Expand the mask so it can be used by the canvas. Any required
5555 // inversion has already been handled.
5556 var destPos = 3; // alpha component offset
5557 for (var j = 0; j < thisChunkHeight; j++) {
5558 var mask = 0;
5559 for (var k = 0; k < width; k++) {
5560 if (!mask) {
5561 var elem = src[srcPos++];
5562 mask = 128;
5563 }
5564 dest[destPos] = (elem & mask) ? 0 : 255;
5565 destPos += 4;
5566 mask >>= 1;
5567 }
5568 }
5569 ctx.putImageData(chunkImgData, 0, i * fullChunkHeight);
5570 }
5571 }
5573 function copyCtxState(sourceCtx, destCtx) {
5574 var properties = ['strokeStyle', 'fillStyle', 'fillRule', 'globalAlpha',
5575 'lineWidth', 'lineCap', 'lineJoin', 'miterLimit',
5576 'globalCompositeOperation', 'font'];
5577 for (var i = 0, ii = properties.length; i < ii; i++) {
5578 var property = properties[i];
5579 if (property in sourceCtx) {
5580 destCtx[property] = sourceCtx[property];
5581 }
5582 }
5583 if ('setLineDash' in sourceCtx) {
5584 destCtx.setLineDash(sourceCtx.getLineDash());
5585 destCtx.lineDashOffset = sourceCtx.lineDashOffset;
5586 } else if ('mozDash' in sourceCtx) {
5587 destCtx.mozDash = sourceCtx.mozDash;
5588 destCtx.mozDashOffset = sourceCtx.mozDashOffset;
5589 }
5590 }
5592 function genericComposeSMask(maskCtx, layerCtx, width, height,
5593 subtype, backdrop) {
5594 var addBackdropFn;
5595 if (backdrop) {
5596 addBackdropFn = function (r0, g0, b0, bytes) {
5597 var length = bytes.length;
5598 for (var i = 3; i < length; i += 4) {
5599 var alpha = bytes[i] / 255;
5600 if (alpha === 0) {
5601 bytes[i - 3] = r0;
5602 bytes[i - 2] = g0;
5603 bytes[i - 1] = b0;
5604 } else if (alpha < 1) {
5605 var alpha_ = 1 - alpha;
5606 bytes[i - 3] = (bytes[i - 3] * alpha + r0 * alpha_) | 0;
5607 bytes[i - 2] = (bytes[i - 2] * alpha + g0 * alpha_) | 0;
5608 bytes[i - 1] = (bytes[i - 1] * alpha + b0 * alpha_) | 0;
5609 }
5610 }
5611 }.bind(null, backdrop[0], backdrop[1], backdrop[2]);
5612 } else {
5613 addBackdropFn = function () {};
5614 }
5616 var composeFn;
5617 if (subtype === 'Luminosity') {
5618 composeFn = function (maskDataBytes, layerDataBytes) {
5619 var length = maskDataBytes.length;
5620 for (var i = 3; i < length; i += 4) {
5621 var y = ((maskDataBytes[i - 3] * 77) + // * 0.3 / 255 * 0x10000
5622 (maskDataBytes[i - 2] * 152) + // * 0.59 ....
5623 (maskDataBytes[i - 1] * 28)) | 0; // * 0.11 ....
5624 layerDataBytes[i] = (layerDataBytes[i] * y) >> 16;
5625 }
5626 };
5627 } else {
5628 composeFn = function (maskDataBytes, layerDataBytes) {
5629 var length = maskDataBytes.length;
5630 for (var i = 3; i < length; i += 4) {
5631 var alpha = maskDataBytes[i];
5632 layerDataBytes[i] = (layerDataBytes[i] * alpha / 255) | 0;
5633 }
5634 };
5635 }
5637 // processing image in chunks to save memory
5638 var PIXELS_TO_PROCESS = 65536;
5639 var chunkSize = Math.min(height, Math.ceil(PIXELS_TO_PROCESS / width));
5640 for (var row = 0; row < height; row += chunkSize) {
5641 var chunkHeight = Math.min(chunkSize, height - row);
5642 var maskData = maskCtx.getImageData(0, row, width, chunkHeight);
5643 var layerData = layerCtx.getImageData(0, row, width, chunkHeight);
5645 addBackdropFn(maskData.data);
5646 composeFn(maskData.data, layerData.data);
5648 maskCtx.putImageData(layerData, 0, row);
5649 }
5650 }
5652 function composeSMask(ctx, smask, layerCtx) {
5653 var mask = smask.canvas;
5654 var maskCtx = smask.context;
5656 ctx.setTransform(smask.scaleX, 0, 0, smask.scaleY,
5657 smask.offsetX, smask.offsetY);
5659 var backdrop;
5660 if (smask.backdrop) {
5661 var cs = smask.colorSpace || ColorSpace.singletons.rgb;
5662 backdrop = cs.getRgb(smask.backdrop, 0);
5663 }
5664 if (WebGLUtils.isEnabled) {
5665 var composed = WebGLUtils.composeSMask(layerCtx.canvas, mask,
5666 {subtype: smask.subtype, backdrop: backdrop});
5667 ctx.setTransform(1, 0, 0, 1, 0, 0);
5668 ctx.drawImage(composed, smask.offsetX, smask.offsetY);
5669 return;
5670 }
5671 genericComposeSMask(maskCtx, layerCtx, mask.width, mask.height,
5672 smask.subtype, backdrop);
5673 ctx.drawImage(mask, 0, 0);
5674 }
5676 var LINE_CAP_STYLES = ['butt', 'round', 'square'];
5677 var LINE_JOIN_STYLES = ['miter', 'round', 'bevel'];
5678 var NORMAL_CLIP = {};
5679 var EO_CLIP = {};
5681 CanvasGraphics.prototype = {
5683 beginDrawing: function CanvasGraphics_beginDrawing(viewport, transparency) {
5684 // For pdfs that use blend modes we have to clear the canvas else certain
5685 // blend modes can look wrong since we'd be blending with a white
5686 // backdrop. The problem with a transparent backdrop though is we then
5687 // don't get sub pixel anti aliasing on text, so we fill with white if
5688 // we can.
5689 var width = this.ctx.canvas.width;
5690 var height = this.ctx.canvas.height;
5691 if (transparency) {
5692 this.ctx.clearRect(0, 0, width, height);
5693 } else {
5694 this.ctx.mozOpaque = true;
5695 this.ctx.save();
5696 this.ctx.fillStyle = 'rgb(255, 255, 255)';
5697 this.ctx.fillRect(0, 0, width, height);
5698 this.ctx.restore();
5699 }
5701 var transform = viewport.transform;
5703 this.ctx.save();
5704 this.ctx.transform.apply(this.ctx, transform);
5706 this.baseTransform = this.ctx.mozCurrentTransform.slice();
5708 if (this.imageLayer) {
5709 this.imageLayer.beginLayout();
5710 }
5711 },
5713 executeOperatorList: function CanvasGraphics_executeOperatorList(
5714 operatorList,
5715 executionStartIdx, continueCallback,
5716 stepper) {
5717 var argsArray = operatorList.argsArray;
5718 var fnArray = operatorList.fnArray;
5719 var i = executionStartIdx || 0;
5720 var argsArrayLen = argsArray.length;
5722 // Sometimes the OperatorList to execute is empty.
5723 if (argsArrayLen == i) {
5724 return i;
5725 }
5727 var endTime = Date.now() + EXECUTION_TIME;
5729 var commonObjs = this.commonObjs;
5730 var objs = this.objs;
5731 var fnId;
5732 var deferred = Promise.resolve();
5734 while (true) {
5735 if (stepper && i === stepper.nextBreakPoint) {
5736 stepper.breakIt(i, continueCallback);
5737 return i;
5738 }
5740 fnId = fnArray[i];
5742 if (fnId !== OPS.dependency) {
5743 this[fnId].apply(this, argsArray[i]);
5744 } else {
5745 var deps = argsArray[i];
5746 for (var n = 0, nn = deps.length; n < nn; n++) {
5747 var depObjId = deps[n];
5748 var common = depObjId.substring(0, 2) == 'g_';
5750 // If the promise isn't resolved yet, add the continueCallback
5751 // to the promise and bail out.
5752 if (!common && !objs.isResolved(depObjId)) {
5753 objs.get(depObjId, continueCallback);
5754 return i;
5755 }
5756 if (common && !commonObjs.isResolved(depObjId)) {
5757 commonObjs.get(depObjId, continueCallback);
5758 return i;
5759 }
5760 }
5761 }
5763 i++;
5765 // If the entire operatorList was executed, stop as were done.
5766 if (i == argsArrayLen) {
5767 return i;
5768 }
5770 // If the execution took longer then a certain amount of time, schedule
5771 // to continue exeution after a short delay.
5772 // However, this is only possible if a 'continueCallback' is passed in.
5773 if (continueCallback && Date.now() > endTime) {
5774 deferred.then(continueCallback);
5775 return i;
5776 }
5778 // If the operatorList isn't executed completely yet OR the execution
5779 // time was short enough, do another execution round.
5780 }
5781 },
5783 endDrawing: function CanvasGraphics_endDrawing() {
5784 this.ctx.restore();
5785 CachedCanvases.clear();
5786 WebGLUtils.clear();
5788 if (this.imageLayer) {
5789 this.imageLayer.endLayout();
5790 }
5791 },
5793 // Graphics state
5794 setLineWidth: function CanvasGraphics_setLineWidth(width) {
5795 this.current.lineWidth = width;
5796 this.ctx.lineWidth = width;
5797 },
5798 setLineCap: function CanvasGraphics_setLineCap(style) {
5799 this.ctx.lineCap = LINE_CAP_STYLES[style];
5800 },
5801 setLineJoin: function CanvasGraphics_setLineJoin(style) {
5802 this.ctx.lineJoin = LINE_JOIN_STYLES[style];
5803 },
5804 setMiterLimit: function CanvasGraphics_setMiterLimit(limit) {
5805 this.ctx.miterLimit = limit;
5806 },
5807 setDash: function CanvasGraphics_setDash(dashArray, dashPhase) {
5808 var ctx = this.ctx;
5809 if ('setLineDash' in ctx) {
5810 ctx.setLineDash(dashArray);
5811 ctx.lineDashOffset = dashPhase;
5812 } else {
5813 ctx.mozDash = dashArray;
5814 ctx.mozDashOffset = dashPhase;
5815 }
5816 },
5817 setRenderingIntent: function CanvasGraphics_setRenderingIntent(intent) {
5818 // Maybe if we one day fully support color spaces this will be important
5819 // for now we can ignore.
5820 // TODO set rendering intent?
5821 },
5822 setFlatness: function CanvasGraphics_setFlatness(flatness) {
5823 // There's no way to control this with canvas, but we can safely ignore.
5824 // TODO set flatness?
5825 },
5826 setGState: function CanvasGraphics_setGState(states) {
5827 for (var i = 0, ii = states.length; i < ii; i++) {
5828 var state = states[i];
5829 var key = state[0];
5830 var value = state[1];
5832 switch (key) {
5833 case 'LW':
5834 this.setLineWidth(value);
5835 break;
5836 case 'LC':
5837 this.setLineCap(value);
5838 break;
5839 case 'LJ':
5840 this.setLineJoin(value);
5841 break;
5842 case 'ML':
5843 this.setMiterLimit(value);
5844 break;
5845 case 'D':
5846 this.setDash(value[0], value[1]);
5847 break;
5848 case 'RI':
5849 this.setRenderingIntent(value);
5850 break;
5851 case 'FL':
5852 this.setFlatness(value);
5853 break;
5854 case 'Font':
5855 this.setFont(value[0], value[1]);
5856 break;
5857 case 'CA':
5858 this.current.strokeAlpha = state[1];
5859 break;
5860 case 'ca':
5861 this.current.fillAlpha = state[1];
5862 this.ctx.globalAlpha = state[1];
5863 break;
5864 case 'BM':
5865 if (value && value.name && (value.name !== 'Normal')) {
5866 var mode = value.name.replace(/([A-Z])/g,
5867 function(c) {
5868 return '-' + c.toLowerCase();
5869 }
5870 ).substring(1);
5871 this.ctx.globalCompositeOperation = mode;
5872 if (this.ctx.globalCompositeOperation !== mode) {
5873 warn('globalCompositeOperation "' + mode +
5874 '" is not supported');
5875 }
5876 } else {
5877 this.ctx.globalCompositeOperation = 'source-over';
5878 }
5879 break;
5880 case 'SMask':
5881 if (this.current.activeSMask) {
5882 this.endSMaskGroup();
5883 }
5884 this.current.activeSMask = value ? this.tempSMask : null;
5885 if (this.current.activeSMask) {
5886 this.beginSMaskGroup();
5887 }
5888 this.tempSMask = null;
5889 break;
5890 }
5891 }
5892 },
5893 beginSMaskGroup: function CanvasGraphics_beginSMaskGroup() {
5895 var activeSMask = this.current.activeSMask;
5896 var drawnWidth = activeSMask.canvas.width;
5897 var drawnHeight = activeSMask.canvas.height;
5898 var cacheId = 'smaskGroupAt' + this.groupLevel;
5899 var scratchCanvas = CachedCanvases.getCanvas(
5900 cacheId, drawnWidth, drawnHeight, true);
5902 var currentCtx = this.ctx;
5903 var currentTransform = currentCtx.mozCurrentTransform;
5904 this.ctx.save();
5906 var groupCtx = scratchCanvas.context;
5907 groupCtx.scale(1 / activeSMask.scaleX, 1 / activeSMask.scaleY);
5908 groupCtx.translate(-activeSMask.offsetX, -activeSMask.offsetY);
5909 groupCtx.transform.apply(groupCtx, currentTransform);
5911 copyCtxState(currentCtx, groupCtx);
5912 this.ctx = groupCtx;
5913 this.setGState([
5914 ['BM', 'Normal'],
5915 ['ca', 1],
5916 ['CA', 1]
5917 ]);
5918 this.groupStack.push(currentCtx);
5919 this.groupLevel++;
5920 },
5921 endSMaskGroup: function CanvasGraphics_endSMaskGroup() {
5922 var groupCtx = this.ctx;
5923 this.groupLevel--;
5924 this.ctx = this.groupStack.pop();
5926 composeSMask(this.ctx, this.current.activeSMask, groupCtx);
5927 this.ctx.restore();
5928 },
5929 save: function CanvasGraphics_save() {
5930 this.ctx.save();
5931 var old = this.current;
5932 this.stateStack.push(old);
5933 this.current = old.clone();
5934 if (this.current.activeSMask) {
5935 this.current.activeSMask = null;
5936 }
5937 },
5938 restore: function CanvasGraphics_restore() {
5939 var prev = this.stateStack.pop();
5940 if (prev) {
5941 if (this.current.activeSMask) {
5942 this.endSMaskGroup();
5943 }
5945 this.current = prev;
5946 this.ctx.restore();
5947 }
5948 },
5949 transform: function CanvasGraphics_transform(a, b, c, d, e, f) {
5950 this.ctx.transform(a, b, c, d, e, f);
5951 },
5953 // Path
5954 moveTo: function CanvasGraphics_moveTo(x, y) {
5955 this.ctx.moveTo(x, y);
5956 this.current.setCurrentPoint(x, y);
5957 },
5958 lineTo: function CanvasGraphics_lineTo(x, y) {
5959 this.ctx.lineTo(x, y);
5960 this.current.setCurrentPoint(x, y);
5961 },
5962 curveTo: function CanvasGraphics_curveTo(x1, y1, x2, y2, x3, y3) {
5963 this.ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3);
5964 this.current.setCurrentPoint(x3, y3);
5965 },
5966 curveTo2: function CanvasGraphics_curveTo2(x2, y2, x3, y3) {
5967 var current = this.current;
5968 this.ctx.bezierCurveTo(current.x, current.y, x2, y2, x3, y3);
5969 current.setCurrentPoint(x3, y3);
5970 },
5971 curveTo3: function CanvasGraphics_curveTo3(x1, y1, x3, y3) {
5972 this.curveTo(x1, y1, x3, y3, x3, y3);
5973 this.current.setCurrentPoint(x3, y3);
5974 },
5975 closePath: function CanvasGraphics_closePath() {
5976 this.ctx.closePath();
5977 },
5978 rectangle: function CanvasGraphics_rectangle(x, y, width, height) {
5979 if (width === 0) {
5980 width = this.getSinglePixelWidth();
5981 }
5982 if (height === 0) {
5983 height = this.getSinglePixelWidth();
5984 }
5986 this.ctx.rect(x, y, width, height);
5987 },
5988 stroke: function CanvasGraphics_stroke(consumePath) {
5989 consumePath = typeof consumePath !== 'undefined' ? consumePath : true;
5990 var ctx = this.ctx;
5991 var strokeColor = this.current.strokeColor;
5992 if (this.current.lineWidth === 0) {
5993 ctx.lineWidth = this.getSinglePixelWidth();
5994 }
5995 // For stroke we want to temporarily change the global alpha to the
5996 // stroking alpha.
5997 ctx.globalAlpha = this.current.strokeAlpha;
5998 if (strokeColor && strokeColor.hasOwnProperty('type') &&
5999 strokeColor.type === 'Pattern') {
6000 // for patterns, we transform to pattern space, calculate
6001 // the pattern, call stroke, and restore to user space
6002 ctx.save();
6003 ctx.strokeStyle = strokeColor.getPattern(ctx, this);
6004 ctx.stroke();
6005 ctx.restore();
6006 } else {
6007 ctx.stroke();
6008 }
6009 if (consumePath) {
6010 this.consumePath();
6011 }
6012 // Restore the global alpha to the fill alpha
6013 ctx.globalAlpha = this.current.fillAlpha;
6014 },
6015 closeStroke: function CanvasGraphics_closeStroke() {
6016 this.closePath();
6017 this.stroke();
6018 },
6019 fill: function CanvasGraphics_fill(consumePath) {
6020 consumePath = typeof consumePath !== 'undefined' ? consumePath : true;
6021 var ctx = this.ctx;
6022 var fillColor = this.current.fillColor;
6023 var needRestore = false;
6025 if (fillColor && fillColor.hasOwnProperty('type') &&
6026 fillColor.type === 'Pattern') {
6027 ctx.save();
6028 ctx.fillStyle = fillColor.getPattern(ctx, this);
6029 needRestore = true;
6030 }
6032 if (this.pendingEOFill) {
6033 if ('mozFillRule' in this.ctx) {
6034 this.ctx.mozFillRule = 'evenodd';
6035 this.ctx.fill();
6036 this.ctx.mozFillRule = 'nonzero';
6037 } else {
6038 try {
6039 this.ctx.fill('evenodd');
6040 } catch (ex) {
6041 // shouldn't really happen, but browsers might think differently
6042 this.ctx.fill();
6043 }
6044 }
6045 this.pendingEOFill = false;
6046 } else {
6047 this.ctx.fill();
6048 }
6050 if (needRestore) {
6051 ctx.restore();
6052 }
6053 if (consumePath) {
6054 this.consumePath();
6055 }
6056 },
6057 eoFill: function CanvasGraphics_eoFill() {
6058 this.pendingEOFill = true;
6059 this.fill();
6060 },
6061 fillStroke: function CanvasGraphics_fillStroke() {
6062 this.fill(false);
6063 this.stroke(false);
6065 this.consumePath();
6066 },
6067 eoFillStroke: function CanvasGraphics_eoFillStroke() {
6068 this.pendingEOFill = true;
6069 this.fillStroke();
6070 },
6071 closeFillStroke: function CanvasGraphics_closeFillStroke() {
6072 this.closePath();
6073 this.fillStroke();
6074 },
6075 closeEOFillStroke: function CanvasGraphics_closeEOFillStroke() {
6076 this.pendingEOFill = true;
6077 this.closePath();
6078 this.fillStroke();
6079 },
6080 endPath: function CanvasGraphics_endPath() {
6081 this.consumePath();
6082 },
6084 // Clipping
6085 clip: function CanvasGraphics_clip() {
6086 this.pendingClip = NORMAL_CLIP;
6087 },
6088 eoClip: function CanvasGraphics_eoClip() {
6089 this.pendingClip = EO_CLIP;
6090 },
6092 // Text
6093 beginText: function CanvasGraphics_beginText() {
6094 this.current.textMatrix = IDENTITY_MATRIX;
6095 this.current.x = this.current.lineX = 0;
6096 this.current.y = this.current.lineY = 0;
6097 },
6098 endText: function CanvasGraphics_endText() {
6099 if (!('pendingTextPaths' in this)) {
6100 this.ctx.beginPath();
6101 return;
6102 }
6103 var paths = this.pendingTextPaths;
6104 var ctx = this.ctx;
6106 ctx.save();
6107 ctx.beginPath();
6108 for (var i = 0; i < paths.length; i++) {
6109 var path = paths[i];
6110 ctx.setTransform.apply(ctx, path.transform);
6111 ctx.translate(path.x, path.y);
6112 path.addToPath(ctx, path.fontSize);
6113 }
6114 ctx.restore();
6115 ctx.clip();
6116 ctx.beginPath();
6117 delete this.pendingTextPaths;
6118 },
6119 setCharSpacing: function CanvasGraphics_setCharSpacing(spacing) {
6120 this.current.charSpacing = spacing;
6121 },
6122 setWordSpacing: function CanvasGraphics_setWordSpacing(spacing) {
6123 this.current.wordSpacing = spacing;
6124 },
6125 setHScale: function CanvasGraphics_setHScale(scale) {
6126 this.current.textHScale = scale / 100;
6127 },
6128 setLeading: function CanvasGraphics_setLeading(leading) {
6129 this.current.leading = -leading;
6130 },
6131 setFont: function CanvasGraphics_setFont(fontRefName, size) {
6132 var fontObj = this.commonObjs.get(fontRefName);
6133 var current = this.current;
6135 if (!fontObj) {
6136 error('Can\'t find font for ' + fontRefName);
6137 }
6139 current.fontMatrix = (fontObj.fontMatrix ?
6140 fontObj.fontMatrix : FONT_IDENTITY_MATRIX);
6142 // A valid matrix needs all main diagonal elements to be non-zero
6143 // This also ensures we bypass FF bugzilla bug #719844.
6144 if (current.fontMatrix[0] === 0 ||
6145 current.fontMatrix[3] === 0) {
6146 warn('Invalid font matrix for font ' + fontRefName);
6147 }
6149 // The spec for Tf (setFont) says that 'size' specifies the font 'scale',
6150 // and in some docs this can be negative (inverted x-y axes).
6151 if (size < 0) {
6152 size = -size;
6153 current.fontDirection = -1;
6154 } else {
6155 current.fontDirection = 1;
6156 }
6158 this.current.font = fontObj;
6159 this.current.fontSize = size;
6161 if (fontObj.coded) {
6162 return; // we don't need ctx.font for Type3 fonts
6163 }
6165 var name = fontObj.loadedName || 'sans-serif';
6166 var bold = fontObj.black ? (fontObj.bold ? 'bolder' : 'bold') :
6167 (fontObj.bold ? 'bold' : 'normal');
6169 var italic = fontObj.italic ? 'italic' : 'normal';
6170 var typeface = '"' + name + '", ' + fontObj.fallbackName;
6172 // Some font backends cannot handle fonts below certain size.
6173 // Keeping the font at minimal size and using the fontSizeScale to change
6174 // the current transformation matrix before the fillText/strokeText.
6175 // See https://bugzilla.mozilla.org/show_bug.cgi?id=726227
6176 var browserFontSize = size >= MIN_FONT_SIZE ? size : MIN_FONT_SIZE;
6177 this.current.fontSizeScale = browserFontSize != MIN_FONT_SIZE ? 1.0 :
6178 size / MIN_FONT_SIZE;
6180 var rule = italic + ' ' + bold + ' ' + browserFontSize + 'px ' + typeface;
6181 this.ctx.font = rule;
6182 },
6183 setTextRenderingMode: function CanvasGraphics_setTextRenderingMode(mode) {
6184 this.current.textRenderingMode = mode;
6185 },
6186 setTextRise: function CanvasGraphics_setTextRise(rise) {
6187 this.current.textRise = rise;
6188 },
6189 moveText: function CanvasGraphics_moveText(x, y) {
6190 this.current.x = this.current.lineX += x;
6191 this.current.y = this.current.lineY += y;
6192 },
6193 setLeadingMoveText: function CanvasGraphics_setLeadingMoveText(x, y) {
6194 this.setLeading(-y);
6195 this.moveText(x, y);
6196 },
6197 setTextMatrix: function CanvasGraphics_setTextMatrix(a, b, c, d, e, f) {
6198 this.current.textMatrix = [a, b, c, d, e, f];
6200 this.current.x = this.current.lineX = 0;
6201 this.current.y = this.current.lineY = 0;
6202 },
6203 nextLine: function CanvasGraphics_nextLine() {
6204 this.moveText(0, this.current.leading);
6205 },
6206 applyTextTransforms: function CanvasGraphics_applyTextTransforms() {
6207 var ctx = this.ctx;
6208 var current = this.current;
6209 ctx.transform.apply(ctx, current.textMatrix);
6210 ctx.translate(current.x, current.y + current.textRise);
6211 if (current.fontDirection > 0) {
6212 ctx.scale(current.textHScale, -1);
6213 } else {
6214 ctx.scale(-current.textHScale, 1);
6215 }
6216 },
6218 paintChar: function (character, x, y) {
6219 var ctx = this.ctx;
6220 var current = this.current;
6221 var font = current.font;
6222 var fontSize = current.fontSize / current.fontSizeScale;
6223 var textRenderingMode = current.textRenderingMode;
6224 var fillStrokeMode = textRenderingMode &
6225 TextRenderingMode.FILL_STROKE_MASK;
6226 var isAddToPathSet = !!(textRenderingMode &
6227 TextRenderingMode.ADD_TO_PATH_FLAG);
6229 var addToPath;
6230 if (font.disableFontFace || isAddToPathSet) {
6231 addToPath = font.getPathGenerator(this.commonObjs, character);
6232 }
6234 if (font.disableFontFace) {
6235 ctx.save();
6236 ctx.translate(x, y);
6237 ctx.beginPath();
6238 addToPath(ctx, fontSize);
6239 if (fillStrokeMode === TextRenderingMode.FILL ||
6240 fillStrokeMode === TextRenderingMode.FILL_STROKE) {
6241 ctx.fill();
6242 }
6243 if (fillStrokeMode === TextRenderingMode.STROKE ||
6244 fillStrokeMode === TextRenderingMode.FILL_STROKE) {
6245 ctx.stroke();
6246 }
6247 ctx.restore();
6248 } else {
6249 if (fillStrokeMode === TextRenderingMode.FILL ||
6250 fillStrokeMode === TextRenderingMode.FILL_STROKE) {
6251 ctx.fillText(character, x, y);
6252 }
6253 if (fillStrokeMode === TextRenderingMode.STROKE ||
6254 fillStrokeMode === TextRenderingMode.FILL_STROKE) {
6255 ctx.strokeText(character, x, y);
6256 }
6257 }
6259 if (isAddToPathSet) {
6260 var paths = this.pendingTextPaths || (this.pendingTextPaths = []);
6261 paths.push({
6262 transform: ctx.mozCurrentTransform,
6263 x: x,
6264 y: y,
6265 fontSize: fontSize,
6266 addToPath: addToPath
6267 });
6268 }
6269 },
6271 get isFontSubpixelAAEnabled() {
6272 // Checks if anti-aliasing is enabled when scaled text is painted.
6273 // On Windows GDI scaled fonts looks bad.
6274 var ctx = document.createElement('canvas').getContext('2d');
6275 ctx.scale(1.5, 1);
6276 ctx.fillText('I', 0, 10);
6277 var data = ctx.getImageData(0, 0, 10, 10).data;
6278 var enabled = false;
6279 for (var i = 3; i < data.length; i += 4) {
6280 if (data[i] > 0 && data[i] < 255) {
6281 enabled = true;
6282 break;
6283 }
6284 }
6285 return shadow(this, 'isFontSubpixelAAEnabled', enabled);
6286 },
6288 showText: function CanvasGraphics_showText(glyphs) {
6289 var ctx = this.ctx;
6290 var current = this.current;
6291 var font = current.font;
6292 var fontSize = current.fontSize;
6293 var fontSizeScale = current.fontSizeScale;
6294 var charSpacing = current.charSpacing;
6295 var wordSpacing = current.wordSpacing;
6296 var textHScale = current.textHScale * current.fontDirection;
6297 var fontMatrix = current.fontMatrix || FONT_IDENTITY_MATRIX;
6298 var glyphsLength = glyphs.length;
6299 var vertical = font.vertical;
6300 var defaultVMetrics = font.defaultVMetrics;
6301 var i, glyph, width;
6303 if (fontSize === 0) {
6304 return;
6305 }
6307 // Type3 fonts - each glyph is a "mini-PDF"
6308 if (font.coded) {
6309 ctx.save();
6310 ctx.transform.apply(ctx, current.textMatrix);
6311 ctx.translate(current.x, current.y);
6313 ctx.scale(textHScale, 1);
6315 for (i = 0; i < glyphsLength; ++i) {
6316 glyph = glyphs[i];
6317 if (glyph === null) {
6318 // word break
6319 this.ctx.translate(wordSpacing, 0);
6320 current.x += wordSpacing * textHScale;
6321 continue;
6322 }
6324 this.processingType3 = glyph;
6325 this.save();
6326 ctx.scale(fontSize, fontSize);
6327 ctx.transform.apply(ctx, fontMatrix);
6328 this.executeOperatorList(glyph.operatorList);
6329 this.restore();
6331 var transformed = Util.applyTransform([glyph.width, 0], fontMatrix);
6332 width = ((transformed[0] * fontSize + charSpacing) *
6333 current.fontDirection);
6335 ctx.translate(width, 0);
6336 current.x += width * textHScale;
6337 }
6338 ctx.restore();
6339 this.processingType3 = null;
6340 } else {
6341 ctx.save();
6342 this.applyTextTransforms();
6344 var lineWidth = current.lineWidth;
6345 var a1 = current.textMatrix[0], b1 = current.textMatrix[1];
6346 var scale = Math.sqrt(a1 * a1 + b1 * b1);
6347 if (scale === 0 || lineWidth === 0) {
6348 lineWidth = this.getSinglePixelWidth();
6349 } else {
6350 lineWidth /= scale;
6351 }
6353 if (fontSizeScale != 1.0) {
6354 ctx.scale(fontSizeScale, fontSizeScale);
6355 lineWidth /= fontSizeScale;
6356 }
6358 ctx.lineWidth = lineWidth;
6360 var x = 0;
6361 for (i = 0; i < glyphsLength; ++i) {
6362 glyph = glyphs[i];
6363 if (glyph === null) {
6364 // word break
6365 x += current.fontDirection * wordSpacing;
6366 continue;
6367 }
6369 var restoreNeeded = false;
6370 var character = glyph.fontChar;
6371 var vmetric = glyph.vmetric || defaultVMetrics;
6372 if (vertical) {
6373 var vx = glyph.vmetric ? vmetric[1] : glyph.width * 0.5;
6374 vx = -vx * fontSize * current.fontMatrix[0];
6375 var vy = vmetric[2] * fontSize * current.fontMatrix[0];
6376 }
6377 width = vmetric ? -vmetric[0] : glyph.width;
6378 var charWidth = width * fontSize * current.fontMatrix[0] +
6379 charSpacing * current.fontDirection;
6380 var accent = glyph.accent;
6382 var scaledX, scaledY, scaledAccentX, scaledAccentY;
6384 if (vertical) {
6385 scaledX = vx / fontSizeScale;
6386 scaledY = (x + vy) / fontSizeScale;
6387 } else {
6388 scaledX = x / fontSizeScale;
6389 scaledY = 0;
6390 }
6392 if (font.remeasure && width > 0 && this.isFontSubpixelAAEnabled) {
6393 // some standard fonts may not have the exact width, trying to
6394 // rescale per character
6395 var measuredWidth = ctx.measureText(character).width * 1000 /
6396 current.fontSize * current.fontSizeScale;
6397 var characterScaleX = width / measuredWidth;
6398 restoreNeeded = true;
6399 ctx.save();
6400 ctx.scale(characterScaleX, 1);
6401 scaledX /= characterScaleX;
6402 if (accent) {
6403 scaledAccentX /= characterScaleX;
6404 }
6405 }
6407 this.paintChar(character, scaledX, scaledY);
6408 if (accent) {
6409 scaledAccentX = scaledX + accent.offset.x / fontSizeScale;
6410 scaledAccentY = scaledY - accent.offset.y / fontSizeScale;
6411 this.paintChar(accent.fontChar, scaledAccentX, scaledAccentY);
6412 }
6414 x += charWidth;
6416 if (restoreNeeded) {
6417 ctx.restore();
6418 }
6419 }
6420 if (vertical) {
6421 current.y -= x * textHScale;
6422 } else {
6423 current.x += x * textHScale;
6424 }
6425 ctx.restore();
6426 }
6427 },
6428 showSpacedText: function CanvasGraphics_showSpacedText(arr) {
6429 var current = this.current;
6430 var font = current.font;
6431 var fontSize = current.fontSize;
6432 // TJ array's number is independent from fontMatrix
6433 var textHScale = current.textHScale * 0.001 * current.fontDirection;
6434 var arrLength = arr.length;
6435 var vertical = font.vertical;
6437 for (var i = 0; i < arrLength; ++i) {
6438 var e = arr[i];
6439 if (isNum(e)) {
6440 var spacingLength = -e * fontSize * textHScale;
6441 if (vertical) {
6442 current.y += spacingLength;
6443 } else {
6444 current.x += spacingLength;
6445 }
6447 } else {
6448 this.showText(e);
6449 }
6450 }
6451 },
6452 nextLineShowText: function CanvasGraphics_nextLineShowText(text) {
6453 this.nextLine();
6454 this.showText(text);
6455 },
6456 nextLineSetSpacingShowText:
6457 function CanvasGraphics_nextLineSetSpacingShowText(wordSpacing,
6458 charSpacing,
6459 text) {
6460 this.setWordSpacing(wordSpacing);
6461 this.setCharSpacing(charSpacing);
6462 this.nextLineShowText(text);
6463 },
6465 // Type3 fonts
6466 setCharWidth: function CanvasGraphics_setCharWidth(xWidth, yWidth) {
6467 // We can safely ignore this since the width should be the same
6468 // as the width in the Widths array.
6469 },
6470 setCharWidthAndBounds: function CanvasGraphics_setCharWidthAndBounds(xWidth,
6471 yWidth,
6472 llx,
6473 lly,
6474 urx,
6475 ury) {
6476 // TODO According to the spec we're also suppose to ignore any operators
6477 // that set color or include images while processing this type3 font.
6478 this.rectangle(llx, lly, urx - llx, ury - lly);
6479 this.clip();
6480 this.endPath();
6481 },
6483 // Color
6484 setStrokeColorSpace: function CanvasGraphics_setStrokeColorSpace(raw) {
6485 this.current.strokeColorSpace = ColorSpace.fromIR(raw);
6486 },
6487 setFillColorSpace: function CanvasGraphics_setFillColorSpace(raw) {
6488 this.current.fillColorSpace = ColorSpace.fromIR(raw);
6489 },
6490 setStrokeColor: function CanvasGraphics_setStrokeColor(/*...*/) {
6491 var cs = this.current.strokeColorSpace;
6492 var rgbColor = cs.getRgb(arguments, 0);
6493 var color = Util.makeCssRgb(rgbColor);
6494 this.ctx.strokeStyle = color;
6495 this.current.strokeColor = color;
6496 },
6497 getColorN_Pattern: function CanvasGraphics_getColorN_Pattern(IR, cs) {
6498 var pattern;
6499 if (IR[0] == 'TilingPattern') {
6500 var args = IR[1];
6501 var base = cs.base;
6502 var color;
6503 if (base) {
6504 color = base.getRgb(args, 0);
6505 }
6506 pattern = new TilingPattern(IR, color, this.ctx, this.objs,
6507 this.commonObjs, this.baseTransform);
6508 } else {
6509 pattern = getShadingPatternFromIR(IR);
6510 }
6511 return pattern;
6512 },
6513 setStrokeColorN: function CanvasGraphics_setStrokeColorN(/*...*/) {
6514 var cs = this.current.strokeColorSpace;
6516 if (cs.name == 'Pattern') {
6517 this.current.strokeColor = this.getColorN_Pattern(arguments, cs);
6518 } else {
6519 this.setStrokeColor.apply(this, arguments);
6520 }
6521 },
6522 setFillColor: function CanvasGraphics_setFillColor(/*...*/) {
6523 var cs = this.current.fillColorSpace;
6524 var rgbColor = cs.getRgb(arguments, 0);
6525 var color = Util.makeCssRgb(rgbColor);
6526 this.ctx.fillStyle = color;
6527 this.current.fillColor = color;
6528 },
6529 setFillColorN: function CanvasGraphics_setFillColorN(/*...*/) {
6530 var cs = this.current.fillColorSpace;
6532 if (cs.name == 'Pattern') {
6533 this.current.fillColor = this.getColorN_Pattern(arguments, cs);
6534 } else {
6535 this.setFillColor.apply(this, arguments);
6536 }
6537 },
6538 setStrokeGray: function CanvasGraphics_setStrokeGray(gray) {
6539 this.current.strokeColorSpace = ColorSpace.singletons.gray;
6541 var rgbColor = this.current.strokeColorSpace.getRgb(arguments, 0);
6542 var color = Util.makeCssRgb(rgbColor);
6543 this.ctx.strokeStyle = color;
6544 this.current.strokeColor = color;
6545 },
6546 setFillGray: function CanvasGraphics_setFillGray(gray) {
6547 this.current.fillColorSpace = ColorSpace.singletons.gray;
6549 var rgbColor = this.current.fillColorSpace.getRgb(arguments, 0);
6550 var color = Util.makeCssRgb(rgbColor);
6551 this.ctx.fillStyle = color;
6552 this.current.fillColor = color;
6553 },
6554 setStrokeRGBColor: function CanvasGraphics_setStrokeRGBColor(r, g, b) {
6555 this.current.strokeColorSpace = ColorSpace.singletons.rgb;
6557 var rgbColor = this.current.strokeColorSpace.getRgb(arguments, 0);
6558 var color = Util.makeCssRgb(rgbColor);
6559 this.ctx.strokeStyle = color;
6560 this.current.strokeColor = color;
6561 },
6562 setFillRGBColor: function CanvasGraphics_setFillRGBColor(r, g, b) {
6563 this.current.fillColorSpace = ColorSpace.singletons.rgb;
6565 var rgbColor = this.current.fillColorSpace.getRgb(arguments, 0);
6566 var color = Util.makeCssRgb(rgbColor);
6567 this.ctx.fillStyle = color;
6568 this.current.fillColor = color;
6569 },
6570 setStrokeCMYKColor: function CanvasGraphics_setStrokeCMYKColor(c, m, y, k) {
6571 this.current.strokeColorSpace = ColorSpace.singletons.cmyk;
6573 var color = Util.makeCssCmyk(arguments);
6574 this.ctx.strokeStyle = color;
6575 this.current.strokeColor = color;
6576 },
6577 setFillCMYKColor: function CanvasGraphics_setFillCMYKColor(c, m, y, k) {
6578 this.current.fillColorSpace = ColorSpace.singletons.cmyk;
6580 var color = Util.makeCssCmyk(arguments);
6581 this.ctx.fillStyle = color;
6582 this.current.fillColor = color;
6583 },
6585 shadingFill: function CanvasGraphics_shadingFill(patternIR) {
6586 var ctx = this.ctx;
6588 this.save();
6589 var pattern = getShadingPatternFromIR(patternIR);
6590 ctx.fillStyle = pattern.getPattern(ctx, this, true);
6592 var inv = ctx.mozCurrentTransformInverse;
6593 if (inv) {
6594 var canvas = ctx.canvas;
6595 var width = canvas.width;
6596 var height = canvas.height;
6598 var bl = Util.applyTransform([0, 0], inv);
6599 var br = Util.applyTransform([0, height], inv);
6600 var ul = Util.applyTransform([width, 0], inv);
6601 var ur = Util.applyTransform([width, height], inv);
6603 var x0 = Math.min(bl[0], br[0], ul[0], ur[0]);
6604 var y0 = Math.min(bl[1], br[1], ul[1], ur[1]);
6605 var x1 = Math.max(bl[0], br[0], ul[0], ur[0]);
6606 var y1 = Math.max(bl[1], br[1], ul[1], ur[1]);
6608 this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0);
6609 } else {
6610 // HACK to draw the gradient onto an infinite rectangle.
6611 // PDF gradients are drawn across the entire image while
6612 // Canvas only allows gradients to be drawn in a rectangle
6613 // The following bug should allow us to remove this.
6614 // https://bugzilla.mozilla.org/show_bug.cgi?id=664884
6616 this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10);
6617 }
6619 this.restore();
6620 },
6622 // Images
6623 beginInlineImage: function CanvasGraphics_beginInlineImage() {
6624 error('Should not call beginInlineImage');
6625 },
6626 beginImageData: function CanvasGraphics_beginImageData() {
6627 error('Should not call beginImageData');
6628 },
6630 paintFormXObjectBegin: function CanvasGraphics_paintFormXObjectBegin(matrix,
6631 bbox) {
6632 this.save();
6633 this.baseTransformStack.push(this.baseTransform);
6635 if (matrix && isArray(matrix) && 6 == matrix.length) {
6636 this.transform.apply(this, matrix);
6637 }
6639 this.baseTransform = this.ctx.mozCurrentTransform;
6641 if (bbox && isArray(bbox) && 4 == bbox.length) {
6642 var width = bbox[2] - bbox[0];
6643 var height = bbox[3] - bbox[1];
6644 this.rectangle(bbox[0], bbox[1], width, height);
6645 this.clip();
6646 this.endPath();
6647 }
6648 },
6650 paintFormXObjectEnd: function CanvasGraphics_paintFormXObjectEnd() {
6651 this.restore();
6652 this.baseTransform = this.baseTransformStack.pop();
6653 },
6655 beginGroup: function CanvasGraphics_beginGroup(group) {
6656 this.save();
6657 var currentCtx = this.ctx;
6658 // TODO non-isolated groups - according to Rik at adobe non-isolated
6659 // group results aren't usually that different and they even have tools
6660 // that ignore this setting. Notes from Rik on implmenting:
6661 // - When you encounter an transparency group, create a new canvas with
6662 // the dimensions of the bbox
6663 // - copy the content from the previous canvas to the new canvas
6664 // - draw as usual
6665 // - remove the backdrop alpha:
6666 // alphaNew = 1 - (1 - alpha)/(1 - alphaBackdrop) with 'alpha' the alpha
6667 // value of your transparency group and 'alphaBackdrop' the alpha of the
6668 // backdrop
6669 // - remove background color:
6670 // colorNew = color - alphaNew *colorBackdrop /(1 - alphaNew)
6671 if (!group.isolated) {
6672 info('TODO: Support non-isolated groups.');
6673 }
6675 // TODO knockout - supposedly possible with the clever use of compositing
6676 // modes.
6677 if (group.knockout) {
6678 warn('Knockout groups not supported.');
6679 }
6681 var currentTransform = currentCtx.mozCurrentTransform;
6682 if (group.matrix) {
6683 currentCtx.transform.apply(currentCtx, group.matrix);
6684 }
6685 assert(group.bbox, 'Bounding box is required.');
6687 // Based on the current transform figure out how big the bounding box
6688 // will actually be.
6689 var bounds = Util.getAxialAlignedBoundingBox(
6690 group.bbox,
6691 currentCtx.mozCurrentTransform);
6692 // Clip the bounding box to the current canvas.
6693 var canvasBounds = [0,
6694 0,
6695 currentCtx.canvas.width,
6696 currentCtx.canvas.height];
6697 bounds = Util.intersect(bounds, canvasBounds) || [0, 0, 0, 0];
6698 // Use ceil in case we're between sizes so we don't create canvas that is
6699 // too small and make the canvas at least 1x1 pixels.
6700 var offsetX = Math.floor(bounds[0]);
6701 var offsetY = Math.floor(bounds[1]);
6702 var drawnWidth = Math.max(Math.ceil(bounds[2]) - offsetX, 1);
6703 var drawnHeight = Math.max(Math.ceil(bounds[3]) - offsetY, 1);
6704 var scaleX = 1, scaleY = 1;
6705 if (drawnWidth > MAX_GROUP_SIZE) {
6706 scaleX = drawnWidth / MAX_GROUP_SIZE;
6707 drawnWidth = MAX_GROUP_SIZE;
6708 }
6709 if (drawnHeight > MAX_GROUP_SIZE) {
6710 scaleY = drawnHeight / MAX_GROUP_SIZE;
6711 drawnHeight = MAX_GROUP_SIZE;
6712 }
6714 var cacheId = 'groupAt' + this.groupLevel;
6715 if (group.smask) {
6716 // Using two cache entries is case if masks are used one after another.
6717 cacheId += '_smask_' + ((this.smaskCounter++) % 2);
6718 }
6719 var scratchCanvas = CachedCanvases.getCanvas(
6720 cacheId, drawnWidth, drawnHeight, true);
6721 var groupCtx = scratchCanvas.context;
6723 // Since we created a new canvas that is just the size of the bounding box
6724 // we have to translate the group ctx.
6725 groupCtx.scale(1 / scaleX, 1 / scaleY);
6726 groupCtx.translate(-offsetX, -offsetY);
6727 groupCtx.transform.apply(groupCtx, currentTransform);
6729 if (group.smask) {
6730 // Saving state and cached mask to be used in setGState.
6731 this.smaskStack.push({
6732 canvas: scratchCanvas.canvas,
6733 context: groupCtx,
6734 offsetX: offsetX,
6735 offsetY: offsetY,
6736 scaleX: scaleX,
6737 scaleY: scaleY,
6738 subtype: group.smask.subtype,
6739 backdrop: group.smask.backdrop,
6740 colorSpace: group.colorSpace && ColorSpace.fromIR(group.colorSpace)
6741 });
6742 } else {
6743 // Setup the current ctx so when the group is popped we draw it at the
6744 // right location.
6745 currentCtx.setTransform(1, 0, 0, 1, 0, 0);
6746 currentCtx.translate(offsetX, offsetY);
6747 currentCtx.scale(scaleX, scaleY);
6748 }
6749 // The transparency group inherits all off the current graphics state
6750 // except the blend mode, soft mask, and alpha constants.
6751 copyCtxState(currentCtx, groupCtx);
6752 this.ctx = groupCtx;
6753 this.setGState([
6754 ['BM', 'Normal'],
6755 ['ca', 1],
6756 ['CA', 1]
6757 ]);
6758 this.groupStack.push(currentCtx);
6759 this.groupLevel++;
6760 },
6762 endGroup: function CanvasGraphics_endGroup(group) {
6763 this.groupLevel--;
6764 var groupCtx = this.ctx;
6765 this.ctx = this.groupStack.pop();
6766 // Turn off image smoothing to avoid sub pixel interpolation which can
6767 // look kind of blurry for some pdfs.
6768 if ('imageSmoothingEnabled' in this.ctx) {
6769 this.ctx.imageSmoothingEnabled = false;
6770 } else {
6771 this.ctx.mozImageSmoothingEnabled = false;
6772 }
6773 if (group.smask) {
6774 this.tempSMask = this.smaskStack.pop();
6775 } else {
6776 this.ctx.drawImage(groupCtx.canvas, 0, 0);
6777 }
6778 this.restore();
6779 },
6781 beginAnnotations: function CanvasGraphics_beginAnnotations() {
6782 this.save();
6783 this.current = new CanvasExtraState();
6784 },
6786 endAnnotations: function CanvasGraphics_endAnnotations() {
6787 this.restore();
6788 },
6790 beginAnnotation: function CanvasGraphics_beginAnnotation(rect, transform,
6791 matrix) {
6792 this.save();
6794 if (rect && isArray(rect) && 4 == rect.length) {
6795 var width = rect[2] - rect[0];
6796 var height = rect[3] - rect[1];
6797 this.rectangle(rect[0], rect[1], width, height);
6798 this.clip();
6799 this.endPath();
6800 }
6802 this.transform.apply(this, transform);
6803 this.transform.apply(this, matrix);
6804 },
6806 endAnnotation: function CanvasGraphics_endAnnotation() {
6807 this.restore();
6808 },
6810 paintJpegXObject: function CanvasGraphics_paintJpegXObject(objId, w, h) {
6811 var domImage = this.objs.get(objId);
6812 if (!domImage) {
6813 warn('Dependent image isn\'t ready yet');
6814 return;
6815 }
6817 this.save();
6819 var ctx = this.ctx;
6820 // scale the image to the unit square
6821 ctx.scale(1 / w, -1 / h);
6823 ctx.drawImage(domImage, 0, 0, domImage.width, domImage.height,
6824 0, -h, w, h);
6825 if (this.imageLayer) {
6826 var currentTransform = ctx.mozCurrentTransformInverse;
6827 var position = this.getCanvasPosition(0, 0);
6828 this.imageLayer.appendImage({
6829 objId: objId,
6830 left: position[0],
6831 top: position[1],
6832 width: w / currentTransform[0],
6833 height: h / currentTransform[3]
6834 });
6835 }
6836 this.restore();
6837 },
6839 paintImageMaskXObject: function CanvasGraphics_paintImageMaskXObject(img) {
6840 var ctx = this.ctx;
6841 var width = img.width, height = img.height;
6843 var glyph = this.processingType3;
6845 if (COMPILE_TYPE3_GLYPHS && glyph && !('compiled' in glyph)) {
6846 var MAX_SIZE_TO_COMPILE = 1000;
6847 if (width <= MAX_SIZE_TO_COMPILE && height <= MAX_SIZE_TO_COMPILE) {
6848 glyph.compiled =
6849 compileType3Glyph({data: img.data, width: width, height: height});
6850 } else {
6851 glyph.compiled = null;
6852 }
6853 }
6855 if (glyph && glyph.compiled) {
6856 glyph.compiled(ctx);
6857 return;
6858 }
6860 var maskCanvas = CachedCanvases.getCanvas('maskCanvas', width, height);
6861 var maskCtx = maskCanvas.context;
6862 maskCtx.save();
6864 putBinaryImageMask(maskCtx, img);
6866 maskCtx.globalCompositeOperation = 'source-in';
6868 var fillColor = this.current.fillColor;
6869 maskCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') &&
6870 fillColor.type === 'Pattern') ?
6871 fillColor.getPattern(maskCtx, this) : fillColor;
6872 maskCtx.fillRect(0, 0, width, height);
6874 maskCtx.restore();
6876 this.paintInlineImageXObject(maskCanvas.canvas);
6877 },
6879 paintImageMaskXObjectRepeat:
6880 function CanvasGraphics_paintImageMaskXObjectRepeat(imgData, scaleX,
6881 scaleY, positions) {
6882 var width = imgData.width;
6883 var height = imgData.height;
6884 var ctx = this.ctx;
6886 var maskCanvas = CachedCanvases.getCanvas('maskCanvas', width, height);
6887 var maskCtx = maskCanvas.context;
6888 maskCtx.save();
6890 putBinaryImageMask(maskCtx, imgData);
6892 maskCtx.globalCompositeOperation = 'source-in';
6894 var fillColor = this.current.fillColor;
6895 maskCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') &&
6896 fillColor.type === 'Pattern') ?
6897 fillColor.getPattern(maskCtx, this) : fillColor;
6898 maskCtx.fillRect(0, 0, width, height);
6900 maskCtx.restore();
6902 for (var i = 0, ii = positions.length; i < ii; i += 2) {
6903 ctx.save();
6904 ctx.transform(scaleX, 0, 0, scaleY, positions[i], positions[i + 1]);
6905 ctx.scale(1, -1);
6906 ctx.drawImage(maskCanvas.canvas, 0, 0, width, height,
6907 0, -1, 1, 1);
6908 ctx.restore();
6909 }
6910 },
6912 paintImageMaskXObjectGroup:
6913 function CanvasGraphics_paintImageMaskXObjectGroup(images) {
6914 var ctx = this.ctx;
6916 for (var i = 0, ii = images.length; i < ii; i++) {
6917 var image = images[i];
6918 var width = image.width, height = image.height;
6920 var maskCanvas = CachedCanvases.getCanvas('maskCanvas', width, height);
6921 var maskCtx = maskCanvas.context;
6922 maskCtx.save();
6924 putBinaryImageMask(maskCtx, image);
6926 maskCtx.globalCompositeOperation = 'source-in';
6928 var fillColor = this.current.fillColor;
6929 maskCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') &&
6930 fillColor.type === 'Pattern') ?
6931 fillColor.getPattern(maskCtx, this) : fillColor;
6932 maskCtx.fillRect(0, 0, width, height);
6934 maskCtx.restore();
6936 ctx.save();
6937 ctx.transform.apply(ctx, image.transform);
6938 ctx.scale(1, -1);
6939 ctx.drawImage(maskCanvas.canvas, 0, 0, width, height,
6940 0, -1, 1, 1);
6941 ctx.restore();
6942 }
6943 },
6945 paintImageXObject: function CanvasGraphics_paintImageXObject(objId) {
6946 var imgData = this.objs.get(objId);
6947 if (!imgData) {
6948 warn('Dependent image isn\'t ready yet');
6949 return;
6950 }
6952 this.paintInlineImageXObject(imgData);
6953 },
6955 paintImageXObjectRepeat:
6956 function CanvasGraphics_paintImageXObjectRepeat(objId, scaleX, scaleY,
6957 positions) {
6958 var imgData = this.objs.get(objId);
6959 if (!imgData) {
6960 warn('Dependent image isn\'t ready yet');
6961 return;
6962 }
6964 var width = imgData.width;
6965 var height = imgData.height;
6966 var map = [];
6967 for (var i = 0, ii = positions.length; i < ii; i += 2) {
6968 map.push({transform: [scaleX, 0, 0, scaleY, positions[i],
6969 positions[i + 1]], x: 0, y: 0, w: width, h: height});
6970 }
6971 this.paintInlineImageXObjectGroup(imgData, map);
6972 },
6974 paintInlineImageXObject:
6975 function CanvasGraphics_paintInlineImageXObject(imgData) {
6976 var width = imgData.width;
6977 var height = imgData.height;
6978 var ctx = this.ctx;
6980 this.save();
6981 // scale the image to the unit square
6982 ctx.scale(1 / width, -1 / height);
6984 var currentTransform = ctx.mozCurrentTransformInverse;
6985 var a = currentTransform[0], b = currentTransform[1];
6986 var widthScale = Math.max(Math.sqrt(a * a + b * b), 1);
6987 var c = currentTransform[2], d = currentTransform[3];
6988 var heightScale = Math.max(Math.sqrt(c * c + d * d), 1);
6990 var imgToPaint, tmpCanvas;
6991 // instanceof HTMLElement does not work in jsdom node.js module
6992 if (imgData instanceof HTMLElement || !imgData.data) {
6993 imgToPaint = imgData;
6994 } else {
6995 tmpCanvas = CachedCanvases.getCanvas('inlineImage', width, height);
6996 var tmpCtx = tmpCanvas.context;
6997 putBinaryImageData(tmpCtx, imgData);
6998 imgToPaint = tmpCanvas.canvas;
6999 }
7001 var paintWidth = width, paintHeight = height;
7002 var tmpCanvasId = 'prescale1';
7003 // Vertial or horizontal scaling shall not be more than 2 to not loose the
7004 // pixels during drawImage operation, painting on the temporary canvas(es)
7005 // that are twice smaller in size
7006 while ((widthScale > 2 && paintWidth > 1) ||
7007 (heightScale > 2 && paintHeight > 1)) {
7008 var newWidth = paintWidth, newHeight = paintHeight;
7009 if (widthScale > 2 && paintWidth > 1) {
7010 newWidth = Math.ceil(paintWidth / 2);
7011 widthScale /= paintWidth / newWidth;
7012 }
7013 if (heightScale > 2 && paintHeight > 1) {
7014 newHeight = Math.ceil(paintHeight / 2);
7015 heightScale /= paintHeight / newHeight;
7016 }
7017 tmpCanvas = CachedCanvases.getCanvas(tmpCanvasId, newWidth, newHeight);
7018 tmpCtx = tmpCanvas.context;
7019 tmpCtx.clearRect(0, 0, newWidth, newHeight);
7020 tmpCtx.drawImage(imgToPaint, 0, 0, paintWidth, paintHeight,
7021 0, 0, newWidth, newHeight);
7022 imgToPaint = tmpCanvas.canvas;
7023 paintWidth = newWidth;
7024 paintHeight = newHeight;
7025 tmpCanvasId = tmpCanvasId === 'prescale1' ? 'prescale2' : 'prescale1';
7026 }
7027 ctx.drawImage(imgToPaint, 0, 0, paintWidth, paintHeight,
7028 0, -height, width, height);
7030 if (this.imageLayer) {
7031 var position = this.getCanvasPosition(0, -height);
7032 this.imageLayer.appendImage({
7033 imgData: imgData,
7034 left: position[0],
7035 top: position[1],
7036 width: width / currentTransform[0],
7037 height: height / currentTransform[3]
7038 });
7039 }
7040 this.restore();
7041 },
7043 paintInlineImageXObjectGroup:
7044 function CanvasGraphics_paintInlineImageXObjectGroup(imgData, map) {
7045 var ctx = this.ctx;
7046 var w = imgData.width;
7047 var h = imgData.height;
7049 var tmpCanvas = CachedCanvases.getCanvas('inlineImage', w, h);
7050 var tmpCtx = tmpCanvas.context;
7051 putBinaryImageData(tmpCtx, imgData);
7053 for (var i = 0, ii = map.length; i < ii; i++) {
7054 var entry = map[i];
7055 ctx.save();
7056 ctx.transform.apply(ctx, entry.transform);
7057 ctx.scale(1, -1);
7058 ctx.drawImage(tmpCanvas.canvas, entry.x, entry.y, entry.w, entry.h,
7059 0, -1, 1, 1);
7060 if (this.imageLayer) {
7061 var position = this.getCanvasPosition(entry.x, entry.y);
7062 this.imageLayer.appendImage({
7063 imgData: imgData,
7064 left: position[0],
7065 top: position[1],
7066 width: w,
7067 height: h
7068 });
7069 }
7070 ctx.restore();
7071 }
7072 },
7074 paintSolidColorImageMask:
7075 function CanvasGraphics_paintSolidColorImageMask() {
7076 this.ctx.fillRect(0, 0, 1, 1);
7077 },
7079 // Marked content
7081 markPoint: function CanvasGraphics_markPoint(tag) {
7082 // TODO Marked content.
7083 },
7084 markPointProps: function CanvasGraphics_markPointProps(tag, properties) {
7085 // TODO Marked content.
7086 },
7087 beginMarkedContent: function CanvasGraphics_beginMarkedContent(tag) {
7088 // TODO Marked content.
7089 },
7090 beginMarkedContentProps: function CanvasGraphics_beginMarkedContentProps(
7091 tag, properties) {
7092 // TODO Marked content.
7093 },
7094 endMarkedContent: function CanvasGraphics_endMarkedContent() {
7095 // TODO Marked content.
7096 },
7098 // Compatibility
7100 beginCompat: function CanvasGraphics_beginCompat() {
7101 // TODO ignore undefined operators (should we do that anyway?)
7102 },
7103 endCompat: function CanvasGraphics_endCompat() {
7104 // TODO stop ignoring undefined operators
7105 },
7107 // Helper functions
7109 consumePath: function CanvasGraphics_consumePath() {
7110 if (this.pendingClip) {
7111 if (this.pendingClip == EO_CLIP) {
7112 if ('mozFillRule' in this.ctx) {
7113 this.ctx.mozFillRule = 'evenodd';
7114 this.ctx.clip();
7115 this.ctx.mozFillRule = 'nonzero';
7116 } else {
7117 try {
7118 this.ctx.clip('evenodd');
7119 } catch (ex) {
7120 // shouldn't really happen, but browsers might think differently
7121 this.ctx.clip();
7122 }
7123 }
7124 } else {
7125 this.ctx.clip();
7126 }
7127 this.pendingClip = null;
7128 }
7129 this.ctx.beginPath();
7130 },
7131 getSinglePixelWidth: function CanvasGraphics_getSinglePixelWidth(scale) {
7132 var inverse = this.ctx.mozCurrentTransformInverse;
7133 // max of the current horizontal and vertical scale
7134 return Math.sqrt(Math.max(
7135 (inverse[0] * inverse[0] + inverse[1] * inverse[1]),
7136 (inverse[2] * inverse[2] + inverse[3] * inverse[3])));
7137 },
7138 getCanvasPosition: function CanvasGraphics_getCanvasPosition(x, y) {
7139 var transform = this.ctx.mozCurrentTransform;
7140 return [
7141 transform[0] * x + transform[2] * y + transform[4],
7142 transform[1] * x + transform[3] * y + transform[5]
7143 ];
7144 }
7145 };
7147 for (var op in OPS) {
7148 CanvasGraphics.prototype[OPS[op]] = CanvasGraphics.prototype[op];
7149 }
7151 return CanvasGraphics;
7152 })();
7156 var WebGLUtils = (function WebGLUtilsClosure() {
7157 function loadShader(gl, code, shaderType) {
7158 var shader = gl.createShader(shaderType);
7159 gl.shaderSource(shader, code);
7160 gl.compileShader(shader);
7161 var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
7162 if (!compiled) {
7163 var errorMsg = gl.getShaderInfoLog(shader);
7164 throw new Error('Error during shader compilation: ' + errorMsg);
7165 }
7166 return shader;
7167 }
7168 function createVertexShader(gl, code) {
7169 return loadShader(gl, code, gl.VERTEX_SHADER);
7170 }
7171 function createFragmentShader(gl, code) {
7172 return loadShader(gl, code, gl.FRAGMENT_SHADER);
7173 }
7174 function createProgram(gl, shaders) {
7175 var program = gl.createProgram();
7176 for (var i = 0, ii = shaders.length; i < ii; ++i) {
7177 gl.attachShader(program, shaders[i]);
7178 }
7179 gl.linkProgram(program);
7180 var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
7181 if (!linked) {
7182 var errorMsg = gl.getProgramInfoLog(program);
7183 throw new Error('Error during program linking: ' + errorMsg);
7184 }
7185 return program;
7186 }
7187 function createTexture(gl, image, textureId) {
7188 gl.activeTexture(textureId);
7189 var texture = gl.createTexture();
7190 gl.bindTexture(gl.TEXTURE_2D, texture);
7192 // Set the parameters so we can render any size image.
7193 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
7194 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
7195 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
7196 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
7198 // Upload the image into the texture.
7199 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
7200 return texture;
7201 }
7203 var currentGL, currentCanvas;
7204 function generageGL() {
7205 if (currentGL) {
7206 return;
7207 }
7208 currentCanvas = document.createElement('canvas');
7209 currentGL = currentCanvas.getContext('webgl',
7210 { premultipliedalpha: false });
7211 }
7213 var smaskVertexShaderCode = '\
7214 attribute vec2 a_position; \
7215 attribute vec2 a_texCoord; \
7216 \
7217 uniform vec2 u_resolution; \
7218 \
7219 varying vec2 v_texCoord; \
7220 \
7221 void main() { \
7222 vec2 clipSpace = (a_position / u_resolution) * 2.0 - 1.0; \
7223 gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1); \
7224 \
7225 v_texCoord = a_texCoord; \
7226 } ';
7228 var smaskFragmentShaderCode = '\
7229 precision mediump float; \
7230 \
7231 uniform vec4 u_backdrop; \
7232 uniform int u_subtype; \
7233 uniform sampler2D u_image; \
7234 uniform sampler2D u_mask; \
7235 \
7236 varying vec2 v_texCoord; \
7237 \
7238 void main() { \
7239 vec4 imageColor = texture2D(u_image, v_texCoord); \
7240 vec4 maskColor = texture2D(u_mask, v_texCoord); \
7241 if (u_backdrop.a > 0.0) { \
7242 maskColor.rgb = maskColor.rgb * maskColor.a + \
7243 u_backdrop.rgb * (1.0 - maskColor.a); \
7244 } \
7245 float lum; \
7246 if (u_subtype == 0) { \
7247 lum = maskColor.a; \
7248 } else { \
7249 lum = maskColor.r * 0.3 + maskColor.g * 0.59 + \
7250 maskColor.b * 0.11; \
7251 } \
7252 imageColor.a *= lum; \
7253 imageColor.rgb *= imageColor.a; \
7254 gl_FragColor = imageColor; \
7255 } ';
7257 var smaskCache = null;
7259 function initSmaskGL() {
7260 var canvas, gl;
7262 generageGL();
7263 canvas = currentCanvas;
7264 currentCanvas = null;
7265 gl = currentGL;
7266 currentGL = null;
7268 // setup a GLSL program
7269 var vertexShader = createVertexShader(gl, smaskVertexShaderCode);
7270 var fragmentShader = createFragmentShader(gl, smaskFragmentShaderCode);
7271 var program = createProgram(gl, [vertexShader, fragmentShader]);
7272 gl.useProgram(program);
7274 var cache = {};
7275 cache.gl = gl;
7276 cache.canvas = canvas;
7277 cache.resolutionLocation = gl.getUniformLocation(program, 'u_resolution');
7278 cache.positionLocation = gl.getAttribLocation(program, 'a_position');
7279 cache.backdropLocation = gl.getUniformLocation(program, 'u_backdrop');
7280 cache.subtypeLocation = gl.getUniformLocation(program, 'u_subtype');
7282 var texCoordLocation = gl.getAttribLocation(program, 'a_texCoord');
7283 var texLayerLocation = gl.getUniformLocation(program, 'u_image');
7284 var texMaskLocation = gl.getUniformLocation(program, 'u_mask');
7286 // provide texture coordinates for the rectangle.
7287 var texCoordBuffer = gl.createBuffer();
7288 gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
7289 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
7290 0.0, 0.0,
7291 1.0, 0.0,
7292 0.0, 1.0,
7293 0.0, 1.0,
7294 1.0, 0.0,
7295 1.0, 1.0]), gl.STATIC_DRAW);
7296 gl.enableVertexAttribArray(texCoordLocation);
7297 gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
7299 gl.uniform1i(texLayerLocation, 0);
7300 gl.uniform1i(texMaskLocation, 1);
7302 smaskCache = cache;
7303 }
7305 function composeSMask(layer, mask, properties) {
7306 var width = layer.width, height = layer.height;
7308 if (!smaskCache) {
7309 initSmaskGL();
7310 }
7311 var cache = smaskCache,canvas = cache.canvas, gl = cache.gl;
7312 canvas.width = width;
7313 canvas.height = height;
7314 gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
7315 gl.uniform2f(cache.resolutionLocation, width, height);
7317 if (properties.backdrop) {
7318 gl.uniform4f(cache.resolutionLocation, properties.backdrop[0],
7319 properties.backdrop[1], properties.backdrop[2], 1);
7320 } else {
7321 gl.uniform4f(cache.resolutionLocation, 0, 0, 0, 0);
7322 }
7323 gl.uniform1i(cache.subtypeLocation,
7324 properties.subtype === 'Luminosity' ? 1 : 0);
7326 // Create a textures
7327 var texture = createTexture(gl, layer, gl.TEXTURE0);
7328 var maskTexture = createTexture(gl, mask, gl.TEXTURE1);
7331 // Create a buffer and put a single clipspace rectangle in
7332 // it (2 triangles)
7333 var buffer = gl.createBuffer();
7334 gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
7335 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
7336 0, 0,
7337 width, 0,
7338 0, height,
7339 0, height,
7340 width, 0,
7341 width, height]), gl.STATIC_DRAW);
7342 gl.enableVertexAttribArray(cache.positionLocation);
7343 gl.vertexAttribPointer(cache.positionLocation, 2, gl.FLOAT, false, 0, 0);
7345 // draw
7346 gl.clearColor(0, 0, 0, 0);
7347 gl.enable(gl.BLEND);
7348 gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
7349 gl.clear(gl.COLOR_BUFFER_BIT);
7351 gl.drawArrays(gl.TRIANGLES, 0, 6);
7353 gl.flush();
7355 gl.deleteTexture(texture);
7356 gl.deleteTexture(maskTexture);
7357 gl.deleteBuffer(buffer);
7359 return canvas;
7360 }
7362 var figuresVertexShaderCode = '\
7363 attribute vec2 a_position; \
7364 attribute vec3 a_color; \
7365 \
7366 uniform vec2 u_resolution; \
7367 uniform vec2 u_scale; \
7368 uniform vec2 u_offset; \
7369 \
7370 varying vec4 v_color; \
7371 \
7372 void main() { \
7373 vec2 position = (a_position + u_offset) * u_scale; \
7374 vec2 clipSpace = (position / u_resolution) * 2.0 - 1.0; \
7375 gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1); \
7376 \
7377 v_color = vec4(a_color / 255.0, 1.0); \
7378 } ';
7380 var figuresFragmentShaderCode = '\
7381 precision mediump float; \
7382 \
7383 varying vec4 v_color; \
7384 \
7385 void main() { \
7386 gl_FragColor = v_color; \
7387 } ';
7389 var figuresCache = null;
7391 function initFiguresGL() {
7392 var canvas, gl;
7394 generageGL();
7395 canvas = currentCanvas;
7396 currentCanvas = null;
7397 gl = currentGL;
7398 currentGL = null;
7400 // setup a GLSL program
7401 var vertexShader = createVertexShader(gl, figuresVertexShaderCode);
7402 var fragmentShader = createFragmentShader(gl, figuresFragmentShaderCode);
7403 var program = createProgram(gl, [vertexShader, fragmentShader]);
7404 gl.useProgram(program);
7406 var cache = {};
7407 cache.gl = gl;
7408 cache.canvas = canvas;
7409 cache.resolutionLocation = gl.getUniformLocation(program, 'u_resolution');
7410 cache.scaleLocation = gl.getUniformLocation(program, 'u_scale');
7411 cache.offsetLocation = gl.getUniformLocation(program, 'u_offset');
7412 cache.positionLocation = gl.getAttribLocation(program, 'a_position');
7413 cache.colorLocation = gl.getAttribLocation(program, 'a_color');
7415 figuresCache = cache;
7416 }
7418 function drawFigures(width, height, backgroundColor, figures, context) {
7419 if (!figuresCache) {
7420 initFiguresGL();
7421 }
7422 var cache = figuresCache, canvas = cache.canvas, gl = cache.gl;
7424 canvas.width = width;
7425 canvas.height = height;
7426 gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
7427 gl.uniform2f(cache.resolutionLocation, width, height);
7429 // count triangle points
7430 var count = 0;
7431 var i, ii, rows;
7432 for (i = 0, ii = figures.length; i < ii; i++) {
7433 switch (figures[i].type) {
7434 case 'lattice':
7435 rows = (figures[i].coords.length / figures[i].verticesPerRow) | 0;
7436 count += (rows - 1) * (figures[i].verticesPerRow - 1) * 6;
7437 break;
7438 case 'triangles':
7439 count += figures[i].coords.length;
7440 break;
7441 }
7442 }
7443 // transfer data
7444 var coords = new Float32Array(count * 2);
7445 var colors = new Uint8Array(count * 3);
7446 var coordsMap = context.coords, colorsMap = context.colors;
7447 var pIndex = 0, cIndex = 0;
7448 for (i = 0, ii = figures.length; i < ii; i++) {
7449 var figure = figures[i], ps = figure.coords, cs = figure.colors;
7450 switch (figure.type) {
7451 case 'lattice':
7452 var cols = figure.verticesPerRow;
7453 rows = (ps.length / cols) | 0;
7454 for (var row = 1; row < rows; row++) {
7455 var offset = row * cols + 1;
7456 for (var col = 1; col < cols; col++, offset++) {
7457 coords[pIndex] = coordsMap[ps[offset - cols - 1]];
7458 coords[pIndex + 1] = coordsMap[ps[offset - cols - 1] + 1];
7459 coords[pIndex + 2] = coordsMap[ps[offset - cols]];
7460 coords[pIndex + 3] = coordsMap[ps[offset - cols] + 1];
7461 coords[pIndex + 4] = coordsMap[ps[offset - 1]];
7462 coords[pIndex + 5] = coordsMap[ps[offset - 1] + 1];
7463 colors[cIndex] = colorsMap[cs[offset - cols - 1]];
7464 colors[cIndex + 1] = colorsMap[cs[offset - cols - 1] + 1];
7465 colors[cIndex + 2] = colorsMap[cs[offset - cols - 1] + 2];
7466 colors[cIndex + 3] = colorsMap[cs[offset - cols]];
7467 colors[cIndex + 4] = colorsMap[cs[offset - cols] + 1];
7468 colors[cIndex + 5] = colorsMap[cs[offset - cols] + 2];
7469 colors[cIndex + 6] = colorsMap[cs[offset - 1]];
7470 colors[cIndex + 7] = colorsMap[cs[offset - 1] + 1];
7471 colors[cIndex + 8] = colorsMap[cs[offset - 1] + 2];
7473 coords[pIndex + 6] = coords[pIndex + 2];
7474 coords[pIndex + 7] = coords[pIndex + 3];
7475 coords[pIndex + 8] = coords[pIndex + 4];
7476 coords[pIndex + 9] = coords[pIndex + 5];
7477 coords[pIndex + 10] = coordsMap[ps[offset]];
7478 coords[pIndex + 11] = coordsMap[ps[offset] + 1];
7479 colors[cIndex + 9] = colors[cIndex + 3];
7480 colors[cIndex + 10] = colors[cIndex + 4];
7481 colors[cIndex + 11] = colors[cIndex + 5];
7482 colors[cIndex + 12] = colors[cIndex + 6];
7483 colors[cIndex + 13] = colors[cIndex + 7];
7484 colors[cIndex + 14] = colors[cIndex + 8];
7485 colors[cIndex + 15] = colorsMap[cs[offset]];
7486 colors[cIndex + 16] = colorsMap[cs[offset] + 1];
7487 colors[cIndex + 17] = colorsMap[cs[offset] + 2];
7488 pIndex += 12;
7489 cIndex += 18;
7490 }
7491 }
7492 break;
7493 case 'triangles':
7494 for (var j = 0, jj = ps.length; j < jj; j++) {
7495 coords[pIndex] = coordsMap[ps[j]];
7496 coords[pIndex + 1] = coordsMap[ps[j] + 1];
7497 colors[cIndex] = colorsMap[cs[i]];
7498 colors[cIndex + 1] = colorsMap[cs[j] + 1];
7499 colors[cIndex + 2] = colorsMap[cs[j] + 2];
7500 pIndex += 2;
7501 cIndex += 3;
7502 }
7503 break;
7504 }
7505 }
7507 // draw
7508 if (backgroundColor) {
7509 gl.clearColor(backgroundColor[0] / 255, backgroundColor[1] / 255,
7510 backgroundColor[2] / 255, 1.0);
7511 } else {
7512 gl.clearColor(0, 0, 0, 0);
7513 }
7514 gl.clear(gl.COLOR_BUFFER_BIT);
7516 var coordsBuffer = gl.createBuffer();
7517 gl.bindBuffer(gl.ARRAY_BUFFER, coordsBuffer);
7518 gl.bufferData(gl.ARRAY_BUFFER, coords, gl.STATIC_DRAW);
7519 gl.enableVertexAttribArray(cache.positionLocation);
7520 gl.vertexAttribPointer(cache.positionLocation, 2, gl.FLOAT, false, 0, 0);
7522 var colorsBuffer = gl.createBuffer();
7523 gl.bindBuffer(gl.ARRAY_BUFFER, colorsBuffer);
7524 gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
7525 gl.enableVertexAttribArray(cache.colorLocation);
7526 gl.vertexAttribPointer(cache.colorLocation, 3, gl.UNSIGNED_BYTE, false,
7527 0, 0);
7529 gl.uniform2f(cache.scaleLocation, context.scaleX, context.scaleY);
7530 gl.uniform2f(cache.offsetLocation, context.offsetX, context.offsetY);
7532 gl.drawArrays(gl.TRIANGLES, 0, count);
7534 gl.flush();
7536 gl.deleteBuffer(coordsBuffer);
7537 gl.deleteBuffer(colorsBuffer);
7539 return canvas;
7540 }
7542 function cleanup() {
7543 smaskCache = null;
7544 figuresCache = null;
7545 }
7547 return {
7548 get isEnabled() {
7549 if (PDFJS.disableWebGL) {
7550 return false;
7551 }
7552 var enabled = false;
7553 try {
7554 generageGL();
7555 enabled = !!currentGL;
7556 } catch (e) { }
7557 return shadow(this, 'isEnabled', enabled);
7558 },
7559 composeSMask: composeSMask,
7560 drawFigures: drawFigures,
7561 clear: cleanup
7562 };
7563 })();
7566 var ShadingIRs = {};
7568 ShadingIRs.RadialAxial = {
7569 fromIR: function RadialAxial_fromIR(raw) {
7570 var type = raw[1];
7571 var colorStops = raw[2];
7572 var p0 = raw[3];
7573 var p1 = raw[4];
7574 var r0 = raw[5];
7575 var r1 = raw[6];
7576 return {
7577 type: 'Pattern',
7578 getPattern: function RadialAxial_getPattern(ctx) {
7579 var grad;
7580 if (type === 'axial') {
7581 grad = ctx.createLinearGradient(p0[0], p0[1], p1[0], p1[1]);
7582 } else if (type === 'radial') {
7583 grad = ctx.createRadialGradient(p0[0], p0[1], r0, p1[0], p1[1], r1);
7584 }
7586 for (var i = 0, ii = colorStops.length; i < ii; ++i) {
7587 var c = colorStops[i];
7588 grad.addColorStop(c[0], c[1]);
7589 }
7590 return grad;
7591 }
7592 };
7593 }
7594 };
7596 var createMeshCanvas = (function createMeshCanvasClosure() {
7597 function drawTriangle(data, context, p1, p2, p3, c1, c2, c3) {
7598 // Very basic Gouraud-shaded triangle rasterization algorithm.
7599 var coords = context.coords, colors = context.colors;
7600 var bytes = data.data, rowSize = data.width * 4;
7601 var tmp;
7602 if (coords[p1 + 1] > coords[p2 + 1]) {
7603 tmp = p1; p1 = p2; p2 = tmp; tmp = c1; c1 = c2; c2 = tmp;
7604 }
7605 if (coords[p2 + 1] > coords[p3 + 1]) {
7606 tmp = p2; p2 = p3; p3 = tmp; tmp = c2; c2 = c3; c3 = tmp;
7607 }
7608 if (coords[p1 + 1] > coords[p2 + 1]) {
7609 tmp = p1; p1 = p2; p2 = tmp; tmp = c1; c1 = c2; c2 = tmp;
7610 }
7611 var x1 = (coords[p1] + context.offsetX) * context.scaleX;
7612 var y1 = (coords[p1 + 1] + context.offsetY) * context.scaleY;
7613 var x2 = (coords[p2] + context.offsetX) * context.scaleX;
7614 var y2 = (coords[p2 + 1] + context.offsetY) * context.scaleY;
7615 var x3 = (coords[p3] + context.offsetX) * context.scaleX;
7616 var y3 = (coords[p3 + 1] + context.offsetY) * context.scaleY;
7617 if (y1 >= y3) {
7618 return;
7619 }
7620 var c1r = colors[c1], c1g = colors[c1 + 1], c1b = colors[c1 + 2];
7621 var c2r = colors[c2], c2g = colors[c2 + 1], c2b = colors[c2 + 2];
7622 var c3r = colors[c3], c3g = colors[c3 + 1], c3b = colors[c3 + 2];
7624 var minY = Math.round(y1), maxY = Math.round(y3);
7625 var xa, car, cag, cab;
7626 var xb, cbr, cbg, cbb;
7627 var k;
7628 for (var y = minY; y <= maxY; y++) {
7629 if (y < y2) {
7630 k = y < y1 ? 0 : y1 === y2 ? 1 : (y1 - y) / (y1 - y2);
7631 xa = x1 - (x1 - x2) * k;
7632 car = c1r - (c1r - c2r) * k;
7633 cag = c1g - (c1g - c2g) * k;
7634 cab = c1b - (c1b - c2b) * k;
7635 } else {
7636 k = y > y3 ? 1 : y2 === y3 ? 0 : (y2 - y) / (y2 - y3);
7637 xa = x2 - (x2 - x3) * k;
7638 car = c2r - (c2r - c3r) * k;
7639 cag = c2g - (c2g - c3g) * k;
7640 cab = c2b - (c2b - c3b) * k;
7641 }
7642 k = y < y1 ? 0 : y > y3 ? 1 : (y1 - y) / (y1 - y3);
7643 xb = x1 - (x1 - x3) * k;
7644 cbr = c1r - (c1r - c3r) * k;
7645 cbg = c1g - (c1g - c3g) * k;
7646 cbb = c1b - (c1b - c3b) * k;
7647 var x1_ = Math.round(Math.min(xa, xb));
7648 var x2_ = Math.round(Math.max(xa, xb));
7649 var j = rowSize * y + x1_ * 4;
7650 for (var x = x1_; x <= x2_; x++) {
7651 k = (xa - x) / (xa - xb);
7652 k = k < 0 ? 0 : k > 1 ? 1 : k;
7653 bytes[j++] = (car - (car - cbr) * k) | 0;
7654 bytes[j++] = (cag - (cag - cbg) * k) | 0;
7655 bytes[j++] = (cab - (cab - cbb) * k) | 0;
7656 bytes[j++] = 255;
7657 }
7658 }
7659 }
7661 function drawFigure(data, figure, context) {
7662 var ps = figure.coords;
7663 var cs = figure.colors;
7664 var i, ii;
7665 switch (figure.type) {
7666 case 'lattice':
7667 var verticesPerRow = figure.verticesPerRow;
7668 var rows = Math.floor(ps.length / verticesPerRow) - 1;
7669 var cols = verticesPerRow - 1;
7670 for (i = 0; i < rows; i++) {
7671 var q = i * verticesPerRow;
7672 for (var j = 0; j < cols; j++, q++) {
7673 drawTriangle(data, context,
7674 ps[q], ps[q + 1], ps[q + verticesPerRow],
7675 cs[q], cs[q + 1], cs[q + verticesPerRow]);
7676 drawTriangle(data, context,
7677 ps[q + verticesPerRow + 1], ps[q + 1], ps[q + verticesPerRow],
7678 cs[q + verticesPerRow + 1], cs[q + 1], cs[q + verticesPerRow]);
7679 }
7680 }
7681 break;
7682 case 'triangles':
7683 for (i = 0, ii = ps.length; i < ii; i += 3) {
7684 drawTriangle(data, context,
7685 ps[i], ps[i + 1], ps[i + 2],
7686 cs[i], cs[i + 1], cs[i + 2]);
7687 }
7688 break;
7689 default:
7690 error('illigal figure');
7691 break;
7692 }
7693 }
7695 function createMeshCanvas(bounds, combinesScale, coords, colors, figures,
7696 backgroundColor) {
7697 // we will increase scale on some weird factor to let antialiasing take
7698 // care of "rough" edges
7699 var EXPECTED_SCALE = 1.1;
7700 // MAX_PATTERN_SIZE is used to avoid OOM situation.
7701 var MAX_PATTERN_SIZE = 3000; // 10in @ 300dpi shall be enough
7703 var offsetX = Math.floor(bounds[0]);
7704 var offsetY = Math.floor(bounds[1]);
7705 var boundsWidth = Math.ceil(bounds[2]) - offsetX;
7706 var boundsHeight = Math.ceil(bounds[3]) - offsetY;
7708 var width = Math.min(Math.ceil(Math.abs(boundsWidth * combinesScale[0] *
7709 EXPECTED_SCALE)), MAX_PATTERN_SIZE);
7710 var height = Math.min(Math.ceil(Math.abs(boundsHeight * combinesScale[1] *
7711 EXPECTED_SCALE)), MAX_PATTERN_SIZE);
7712 var scaleX = boundsWidth / width;
7713 var scaleY = boundsHeight / height;
7715 var context = {
7716 coords: coords,
7717 colors: colors,
7718 offsetX: -offsetX,
7719 offsetY: -offsetY,
7720 scaleX: 1 / scaleX,
7721 scaleY: 1 / scaleY
7722 };
7724 var canvas, tmpCanvas, i, ii;
7725 if (WebGLUtils.isEnabled) {
7726 canvas = WebGLUtils.drawFigures(width, height, backgroundColor,
7727 figures, context);
7729 // https://bugzilla.mozilla.org/show_bug.cgi?id=972126
7730 tmpCanvas = CachedCanvases.getCanvas('mesh', width, height, false);
7731 tmpCanvas.context.drawImage(canvas, 0, 0);
7732 canvas = tmpCanvas.canvas;
7733 } else {
7734 tmpCanvas = CachedCanvases.getCanvas('mesh', width, height, false);
7735 var tmpCtx = tmpCanvas.context;
7737 var data = tmpCtx.createImageData(width, height);
7738 if (backgroundColor) {
7739 var bytes = data.data;
7740 for (i = 0, ii = bytes.length; i < ii; i += 4) {
7741 bytes[i] = backgroundColor[0];
7742 bytes[i + 1] = backgroundColor[1];
7743 bytes[i + 2] = backgroundColor[2];
7744 bytes[i + 3] = 255;
7745 }
7746 }
7747 for (i = 0; i < figures.length; i++) {
7748 drawFigure(data, figures[i], context);
7749 }
7750 tmpCtx.putImageData(data, 0, 0);
7751 canvas = tmpCanvas.canvas;
7752 }
7754 return {canvas: canvas, offsetX: offsetX, offsetY: offsetY,
7755 scaleX: scaleX, scaleY: scaleY};
7756 }
7757 return createMeshCanvas;
7758 })();
7760 ShadingIRs.Mesh = {
7761 fromIR: function Mesh_fromIR(raw) {
7762 //var type = raw[1];
7763 var coords = raw[2];
7764 var colors = raw[3];
7765 var figures = raw[4];
7766 var bounds = raw[5];
7767 var matrix = raw[6];
7768 //var bbox = raw[7];
7769 var background = raw[8];
7770 return {
7771 type: 'Pattern',
7772 getPattern: function Mesh_getPattern(ctx, owner, shadingFill) {
7773 var combinedScale;
7774 // Obtain scale from matrix and current transformation matrix.
7775 if (shadingFill) {
7776 combinedScale = Util.singularValueDecompose2dScale(
7777 ctx.mozCurrentTransform);
7778 } else {
7779 var matrixScale = Util.singularValueDecompose2dScale(matrix);
7780 var curMatrixScale = Util.singularValueDecompose2dScale(
7781 owner.baseTransform);
7782 combinedScale = [matrixScale[0] * curMatrixScale[0],
7783 matrixScale[1] * curMatrixScale[1]];
7784 }
7787 // Rasterizing on the main thread since sending/queue large canvases
7788 // might cause OOM.
7789 var temporaryPatternCanvas = createMeshCanvas(bounds, combinedScale,
7790 coords, colors, figures, shadingFill ? null : background);
7792 if (!shadingFill) {
7793 ctx.setTransform.apply(ctx, owner.baseTransform);
7794 if (matrix) {
7795 ctx.transform.apply(ctx, matrix);
7796 }
7797 }
7799 ctx.translate(temporaryPatternCanvas.offsetX,
7800 temporaryPatternCanvas.offsetY);
7801 ctx.scale(temporaryPatternCanvas.scaleX,
7802 temporaryPatternCanvas.scaleY);
7804 return ctx.createPattern(temporaryPatternCanvas.canvas, 'no-repeat');
7805 }
7806 };
7807 }
7808 };
7810 ShadingIRs.Dummy = {
7811 fromIR: function Dummy_fromIR() {
7812 return {
7813 type: 'Pattern',
7814 getPattern: function Dummy_fromIR_getPattern() {
7815 return 'hotpink';
7816 }
7817 };
7818 }
7819 };
7821 function getShadingPatternFromIR(raw) {
7822 var shadingIR = ShadingIRs[raw[0]];
7823 if (!shadingIR) {
7824 error('Unknown IR type: ' + raw[0]);
7825 }
7826 return shadingIR.fromIR(raw);
7827 }
7829 var TilingPattern = (function TilingPatternClosure() {
7830 var PaintType = {
7831 COLORED: 1,
7832 UNCOLORED: 2
7833 };
7835 var MAX_PATTERN_SIZE = 3000; // 10in @ 300dpi shall be enough
7837 function TilingPattern(IR, color, ctx, objs, commonObjs, baseTransform) {
7838 this.name = IR[1][0].name;
7839 this.operatorList = IR[2];
7840 this.matrix = IR[3] || [1, 0, 0, 1, 0, 0];
7841 this.bbox = IR[4];
7842 this.xstep = IR[5];
7843 this.ystep = IR[6];
7844 this.paintType = IR[7];
7845 this.tilingType = IR[8];
7846 this.color = color;
7847 this.objs = objs;
7848 this.commonObjs = commonObjs;
7849 this.baseTransform = baseTransform;
7850 this.type = 'Pattern';
7851 this.ctx = ctx;
7852 }
7854 TilingPattern.prototype = {
7855 createPatternCanvas: function TilinPattern_createPatternCanvas(owner) {
7856 var operatorList = this.operatorList;
7857 var bbox = this.bbox;
7858 var xstep = this.xstep;
7859 var ystep = this.ystep;
7860 var paintType = this.paintType;
7861 var tilingType = this.tilingType;
7862 var color = this.color;
7863 var objs = this.objs;
7864 var commonObjs = this.commonObjs;
7866 info('TilingType: ' + tilingType);
7868 var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3];
7870 var topLeft = [x0, y0];
7871 // we want the canvas to be as large as the step size
7872 var botRight = [x0 + xstep, y0 + ystep];
7874 var width = botRight[0] - topLeft[0];
7875 var height = botRight[1] - topLeft[1];
7877 // Obtain scale from matrix and current transformation matrix.
7878 var matrixScale = Util.singularValueDecompose2dScale(this.matrix);
7879 var curMatrixScale = Util.singularValueDecompose2dScale(
7880 this.baseTransform);
7881 var combinedScale = [matrixScale[0] * curMatrixScale[0],
7882 matrixScale[1] * curMatrixScale[1]];
7884 // MAX_PATTERN_SIZE is used to avoid OOM situation.
7885 // Use width and height values that are as close as possible to the end
7886 // result when the pattern is used. Too low value makes the pattern look
7887 // blurry. Too large value makes it look too crispy.
7888 width = Math.min(Math.ceil(Math.abs(width * combinedScale[0])),
7889 MAX_PATTERN_SIZE);
7891 height = Math.min(Math.ceil(Math.abs(height * combinedScale[1])),
7892 MAX_PATTERN_SIZE);
7894 var tmpCanvas = CachedCanvases.getCanvas('pattern', width, height, true);
7895 var tmpCtx = tmpCanvas.context;
7896 var graphics = new CanvasGraphics(tmpCtx, commonObjs, objs);
7897 graphics.groupLevel = owner.groupLevel;
7899 this.setFillAndStrokeStyleToContext(tmpCtx, paintType, color);
7901 this.setScale(width, height, xstep, ystep);
7902 this.transformToScale(graphics);
7904 // transform coordinates to pattern space
7905 var tmpTranslate = [1, 0, 0, 1, -topLeft[0], -topLeft[1]];
7906 graphics.transform.apply(graphics, tmpTranslate);
7908 this.clipBbox(graphics, bbox, x0, y0, x1, y1);
7910 graphics.executeOperatorList(operatorList);
7911 return tmpCanvas.canvas;
7912 },
7914 setScale: function TilingPattern_setScale(width, height, xstep, ystep) {
7915 this.scale = [width / xstep, height / ystep];
7916 },
7918 transformToScale: function TilingPattern_transformToScale(graphics) {
7919 var scale = this.scale;
7920 var tmpScale = [scale[0], 0, 0, scale[1], 0, 0];
7921 graphics.transform.apply(graphics, tmpScale);
7922 },
7924 scaleToContext: function TilingPattern_scaleToContext() {
7925 var scale = this.scale;
7926 this.ctx.scale(1 / scale[0], 1 / scale[1]);
7927 },
7929 clipBbox: function clipBbox(graphics, bbox, x0, y0, x1, y1) {
7930 if (bbox && isArray(bbox) && 4 == bbox.length) {
7931 var bboxWidth = x1 - x0;
7932 var bboxHeight = y1 - y0;
7933 graphics.rectangle(x0, y0, bboxWidth, bboxHeight);
7934 graphics.clip();
7935 graphics.endPath();
7936 }
7937 },
7939 setFillAndStrokeStyleToContext:
7940 function setFillAndStrokeStyleToContext(context, paintType, color) {
7941 switch (paintType) {
7942 case PaintType.COLORED:
7943 var ctx = this.ctx;
7944 context.fillStyle = ctx.fillStyle;
7945 context.strokeStyle = ctx.strokeStyle;
7946 break;
7947 case PaintType.UNCOLORED:
7948 var rgbColor = ColorSpace.singletons.rgb.getRgb(color, 0);
7949 var cssColor = Util.makeCssRgb(rgbColor);
7950 context.fillStyle = cssColor;
7951 context.strokeStyle = cssColor;
7952 break;
7953 default:
7954 error('Unsupported paint type: ' + paintType);
7955 }
7956 },
7958 getPattern: function TilingPattern_getPattern(ctx, owner) {
7959 var temporaryPatternCanvas = this.createPatternCanvas(owner);
7961 ctx = this.ctx;
7962 ctx.setTransform.apply(ctx, this.baseTransform);
7963 ctx.transform.apply(ctx, this.matrix);
7964 this.scaleToContext();
7966 return ctx.createPattern(temporaryPatternCanvas, 'repeat');
7967 }
7968 };
7970 return TilingPattern;
7971 })();
7974 PDFJS.disableFontFace = false;
7976 var FontLoader = {
7977 insertRule: function fontLoaderInsertRule(rule) {
7978 var styleElement = document.getElementById('PDFJS_FONT_STYLE_TAG');
7979 if (!styleElement) {
7980 styleElement = document.createElement('style');
7981 styleElement.id = 'PDFJS_FONT_STYLE_TAG';
7982 document.documentElement.getElementsByTagName('head')[0].appendChild(
7983 styleElement);
7984 }
7986 var styleSheet = styleElement.sheet;
7987 styleSheet.insertRule(rule, styleSheet.cssRules.length);
7988 },
7990 clear: function fontLoaderClear() {
7991 var styleElement = document.getElementById('PDFJS_FONT_STYLE_TAG');
7992 if (styleElement) {
7993 styleElement.parentNode.removeChild(styleElement);
7994 }
7995 },
7996 bind: function fontLoaderBind(fonts, callback) {
7997 assert(!isWorker, 'bind() shall be called from main thread');
7999 for (var i = 0, ii = fonts.length; i < ii; i++) {
8000 var font = fonts[i];
8001 if (font.attached) {
8002 continue;
8003 }
8005 font.attached = true;
8006 font.bindDOM()
8007 }
8009 setTimeout(callback);
8010 }
8011 };
8013 var FontFace = (function FontFaceClosure() {
8014 function FontFace(name, file, properties) {
8015 this.compiledGlyphs = {};
8016 if (arguments.length === 1) {
8017 // importing translated data
8018 var data = arguments[0];
8019 for (var i in data) {
8020 this[i] = data[i];
8021 }
8022 return;
8023 }
8024 }
8025 FontFace.prototype = {
8026 bindDOM: function FontFace_bindDOM() {
8027 if (!this.data) {
8028 return null;
8029 }
8031 if (PDFJS.disableFontFace) {
8032 this.disableFontFace = true;
8033 return null;
8034 }
8036 var data = bytesToString(new Uint8Array(this.data));
8037 var fontName = this.loadedName;
8039 // Add the font-face rule to the document
8040 var url = ('url(data:' + this.mimetype + ';base64,' +
8041 window.btoa(data) + ');');
8042 var rule = '@font-face { font-family:"' + fontName + '";src:' + url + '}';
8043 FontLoader.insertRule(rule);
8045 if (PDFJS.pdfBug && 'FontInspector' in globalScope &&
8046 globalScope['FontInspector'].enabled) {
8047 globalScope['FontInspector'].fontAdded(this, url);
8048 }
8050 return rule;
8051 },
8053 getPathGenerator: function (objs, character) {
8054 if (!(character in this.compiledGlyphs)) {
8055 var js = objs.get(this.loadedName + '_path_' + character);
8056 /*jshint -W054 */
8057 this.compiledGlyphs[character] = new Function('c', 'size', js);
8058 }
8059 return this.compiledGlyphs[character];
8060 }
8061 };
8062 return FontFace;
8063 })();
8066 }).call((typeof window === 'undefined') ? this : window);