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.
michael@0 | 1 | /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ |
michael@0 | 3 | /* Copyright 2012 Mozilla Foundation |
michael@0 | 4 | * |
michael@0 | 5 | * Licensed under the Apache License, Version 2.0 (the "License"); |
michael@0 | 6 | * you may not use this file except in compliance with the License. |
michael@0 | 7 | * You may obtain a copy of the License at |
michael@0 | 8 | * |
michael@0 | 9 | * http://www.apache.org/licenses/LICENSE-2.0 |
michael@0 | 10 | * |
michael@0 | 11 | * Unless required by applicable law or agreed to in writing, software |
michael@0 | 12 | * distributed under the License is distributed on an "AS IS" BASIS, |
michael@0 | 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
michael@0 | 14 | * See the License for the specific language governing permissions and |
michael@0 | 15 | * limitations under the License. |
michael@0 | 16 | */ |
michael@0 | 17 | /*jshint globalstrict: false */ |
michael@0 | 18 | |
michael@0 | 19 | // Initializing PDFJS global object (if still undefined) |
michael@0 | 20 | if (typeof PDFJS === 'undefined') { |
michael@0 | 21 | (typeof window !== 'undefined' ? window : this).PDFJS = {}; |
michael@0 | 22 | } |
michael@0 | 23 | |
michael@0 | 24 | PDFJS.version = '1.0.68'; |
michael@0 | 25 | PDFJS.build = 'ead4cbf'; |
michael@0 | 26 | |
michael@0 | 27 | (function pdfjsWrapper() { |
michael@0 | 28 | // Use strict in our context only - users might not want it |
michael@0 | 29 | 'use strict'; |
michael@0 | 30 | |
michael@0 | 31 | /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 32 | /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ |
michael@0 | 33 | /* Copyright 2012 Mozilla Foundation |
michael@0 | 34 | * |
michael@0 | 35 | * Licensed under the Apache License, Version 2.0 (the "License"); |
michael@0 | 36 | * you may not use this file except in compliance with the License. |
michael@0 | 37 | * You may obtain a copy of the License at |
michael@0 | 38 | * |
michael@0 | 39 | * http://www.apache.org/licenses/LICENSE-2.0 |
michael@0 | 40 | * |
michael@0 | 41 | * Unless required by applicable law or agreed to in writing, software |
michael@0 | 42 | * distributed under the License is distributed on an "AS IS" BASIS, |
michael@0 | 43 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
michael@0 | 44 | * See the License for the specific language governing permissions and |
michael@0 | 45 | * limitations under the License. |
michael@0 | 46 | */ |
michael@0 | 47 | /* globals Cmd, ColorSpace, Dict, MozBlobBuilder, Name, PDFJS, Ref, URL, |
michael@0 | 48 | Promise */ |
michael@0 | 49 | |
michael@0 | 50 | 'use strict'; |
michael@0 | 51 | |
michael@0 | 52 | var globalScope = (typeof window === 'undefined') ? this : window; |
michael@0 | 53 | |
michael@0 | 54 | var isWorker = (typeof window == 'undefined'); |
michael@0 | 55 | |
michael@0 | 56 | var FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0]; |
michael@0 | 57 | |
michael@0 | 58 | var TextRenderingMode = { |
michael@0 | 59 | FILL: 0, |
michael@0 | 60 | STROKE: 1, |
michael@0 | 61 | FILL_STROKE: 2, |
michael@0 | 62 | INVISIBLE: 3, |
michael@0 | 63 | FILL_ADD_TO_PATH: 4, |
michael@0 | 64 | STROKE_ADD_TO_PATH: 5, |
michael@0 | 65 | FILL_STROKE_ADD_TO_PATH: 6, |
michael@0 | 66 | ADD_TO_PATH: 7, |
michael@0 | 67 | FILL_STROKE_MASK: 3, |
michael@0 | 68 | ADD_TO_PATH_FLAG: 4 |
michael@0 | 69 | }; |
michael@0 | 70 | |
michael@0 | 71 | var ImageKind = { |
michael@0 | 72 | GRAYSCALE_1BPP: 1, |
michael@0 | 73 | RGB_24BPP: 2, |
michael@0 | 74 | RGBA_32BPP: 3 |
michael@0 | 75 | }; |
michael@0 | 76 | |
michael@0 | 77 | // The global PDFJS object exposes the API |
michael@0 | 78 | // In production, it will be declared outside a global wrapper |
michael@0 | 79 | // In development, it will be declared here |
michael@0 | 80 | if (!globalScope.PDFJS) { |
michael@0 | 81 | globalScope.PDFJS = {}; |
michael@0 | 82 | } |
michael@0 | 83 | |
michael@0 | 84 | globalScope.PDFJS.pdfBug = false; |
michael@0 | 85 | |
michael@0 | 86 | PDFJS.VERBOSITY_LEVELS = { |
michael@0 | 87 | errors: 0, |
michael@0 | 88 | warnings: 1, |
michael@0 | 89 | infos: 5 |
michael@0 | 90 | }; |
michael@0 | 91 | |
michael@0 | 92 | // All the possible operations for an operator list. |
michael@0 | 93 | var OPS = PDFJS.OPS = { |
michael@0 | 94 | // Intentionally start from 1 so it is easy to spot bad operators that will be |
michael@0 | 95 | // 0's. |
michael@0 | 96 | dependency: 1, |
michael@0 | 97 | setLineWidth: 2, |
michael@0 | 98 | setLineCap: 3, |
michael@0 | 99 | setLineJoin: 4, |
michael@0 | 100 | setMiterLimit: 5, |
michael@0 | 101 | setDash: 6, |
michael@0 | 102 | setRenderingIntent: 7, |
michael@0 | 103 | setFlatness: 8, |
michael@0 | 104 | setGState: 9, |
michael@0 | 105 | save: 10, |
michael@0 | 106 | restore: 11, |
michael@0 | 107 | transform: 12, |
michael@0 | 108 | moveTo: 13, |
michael@0 | 109 | lineTo: 14, |
michael@0 | 110 | curveTo: 15, |
michael@0 | 111 | curveTo2: 16, |
michael@0 | 112 | curveTo3: 17, |
michael@0 | 113 | closePath: 18, |
michael@0 | 114 | rectangle: 19, |
michael@0 | 115 | stroke: 20, |
michael@0 | 116 | closeStroke: 21, |
michael@0 | 117 | fill: 22, |
michael@0 | 118 | eoFill: 23, |
michael@0 | 119 | fillStroke: 24, |
michael@0 | 120 | eoFillStroke: 25, |
michael@0 | 121 | closeFillStroke: 26, |
michael@0 | 122 | closeEOFillStroke: 27, |
michael@0 | 123 | endPath: 28, |
michael@0 | 124 | clip: 29, |
michael@0 | 125 | eoClip: 30, |
michael@0 | 126 | beginText: 31, |
michael@0 | 127 | endText: 32, |
michael@0 | 128 | setCharSpacing: 33, |
michael@0 | 129 | setWordSpacing: 34, |
michael@0 | 130 | setHScale: 35, |
michael@0 | 131 | setLeading: 36, |
michael@0 | 132 | setFont: 37, |
michael@0 | 133 | setTextRenderingMode: 38, |
michael@0 | 134 | setTextRise: 39, |
michael@0 | 135 | moveText: 40, |
michael@0 | 136 | setLeadingMoveText: 41, |
michael@0 | 137 | setTextMatrix: 42, |
michael@0 | 138 | nextLine: 43, |
michael@0 | 139 | showText: 44, |
michael@0 | 140 | showSpacedText: 45, |
michael@0 | 141 | nextLineShowText: 46, |
michael@0 | 142 | nextLineSetSpacingShowText: 47, |
michael@0 | 143 | setCharWidth: 48, |
michael@0 | 144 | setCharWidthAndBounds: 49, |
michael@0 | 145 | setStrokeColorSpace: 50, |
michael@0 | 146 | setFillColorSpace: 51, |
michael@0 | 147 | setStrokeColor: 52, |
michael@0 | 148 | setStrokeColorN: 53, |
michael@0 | 149 | setFillColor: 54, |
michael@0 | 150 | setFillColorN: 55, |
michael@0 | 151 | setStrokeGray: 56, |
michael@0 | 152 | setFillGray: 57, |
michael@0 | 153 | setStrokeRGBColor: 58, |
michael@0 | 154 | setFillRGBColor: 59, |
michael@0 | 155 | setStrokeCMYKColor: 60, |
michael@0 | 156 | setFillCMYKColor: 61, |
michael@0 | 157 | shadingFill: 62, |
michael@0 | 158 | beginInlineImage: 63, |
michael@0 | 159 | beginImageData: 64, |
michael@0 | 160 | endInlineImage: 65, |
michael@0 | 161 | paintXObject: 66, |
michael@0 | 162 | markPoint: 67, |
michael@0 | 163 | markPointProps: 68, |
michael@0 | 164 | beginMarkedContent: 69, |
michael@0 | 165 | beginMarkedContentProps: 70, |
michael@0 | 166 | endMarkedContent: 71, |
michael@0 | 167 | beginCompat: 72, |
michael@0 | 168 | endCompat: 73, |
michael@0 | 169 | paintFormXObjectBegin: 74, |
michael@0 | 170 | paintFormXObjectEnd: 75, |
michael@0 | 171 | beginGroup: 76, |
michael@0 | 172 | endGroup: 77, |
michael@0 | 173 | beginAnnotations: 78, |
michael@0 | 174 | endAnnotations: 79, |
michael@0 | 175 | beginAnnotation: 80, |
michael@0 | 176 | endAnnotation: 81, |
michael@0 | 177 | paintJpegXObject: 82, |
michael@0 | 178 | paintImageMaskXObject: 83, |
michael@0 | 179 | paintImageMaskXObjectGroup: 84, |
michael@0 | 180 | paintImageXObject: 85, |
michael@0 | 181 | paintInlineImageXObject: 86, |
michael@0 | 182 | paintInlineImageXObjectGroup: 87, |
michael@0 | 183 | paintImageXObjectRepeat: 88, |
michael@0 | 184 | paintImageMaskXObjectRepeat: 89, |
michael@0 | 185 | paintSolidColorImageMask: 90 |
michael@0 | 186 | }; |
michael@0 | 187 | |
michael@0 | 188 | // A notice for devs. These are good for things that are helpful to devs, such |
michael@0 | 189 | // as warning that Workers were disabled, which is important to devs but not |
michael@0 | 190 | // end users. |
michael@0 | 191 | function info(msg) { |
michael@0 | 192 | if (PDFJS.verbosity >= PDFJS.VERBOSITY_LEVELS.infos) { |
michael@0 | 193 | console.log('Info: ' + msg); |
michael@0 | 194 | } |
michael@0 | 195 | } |
michael@0 | 196 | |
michael@0 | 197 | // Non-fatal warnings. |
michael@0 | 198 | function warn(msg) { |
michael@0 | 199 | if (PDFJS.verbosity >= PDFJS.VERBOSITY_LEVELS.warnings) { |
michael@0 | 200 | console.log('Warning: ' + msg); |
michael@0 | 201 | } |
michael@0 | 202 | } |
michael@0 | 203 | |
michael@0 | 204 | // Fatal errors that should trigger the fallback UI and halt execution by |
michael@0 | 205 | // throwing an exception. |
michael@0 | 206 | function error(msg) { |
michael@0 | 207 | // If multiple arguments were passed, pass them all to the log function. |
michael@0 | 208 | if (arguments.length > 1) { |
michael@0 | 209 | var logArguments = ['Error:']; |
michael@0 | 210 | logArguments.push.apply(logArguments, arguments); |
michael@0 | 211 | console.log.apply(console, logArguments); |
michael@0 | 212 | // Join the arguments into a single string for the lines below. |
michael@0 | 213 | msg = [].join.call(arguments, ' '); |
michael@0 | 214 | } else { |
michael@0 | 215 | console.log('Error: ' + msg); |
michael@0 | 216 | } |
michael@0 | 217 | console.log(backtrace()); |
michael@0 | 218 | UnsupportedManager.notify(UNSUPPORTED_FEATURES.unknown); |
michael@0 | 219 | throw new Error(msg); |
michael@0 | 220 | } |
michael@0 | 221 | |
michael@0 | 222 | function backtrace() { |
michael@0 | 223 | try { |
michael@0 | 224 | throw new Error(); |
michael@0 | 225 | } catch (e) { |
michael@0 | 226 | return e.stack ? e.stack.split('\n').slice(2).join('\n') : ''; |
michael@0 | 227 | } |
michael@0 | 228 | } |
michael@0 | 229 | |
michael@0 | 230 | function assert(cond, msg) { |
michael@0 | 231 | if (!cond) { |
michael@0 | 232 | error(msg); |
michael@0 | 233 | } |
michael@0 | 234 | } |
michael@0 | 235 | |
michael@0 | 236 | var UNSUPPORTED_FEATURES = PDFJS.UNSUPPORTED_FEATURES = { |
michael@0 | 237 | unknown: 'unknown', |
michael@0 | 238 | forms: 'forms', |
michael@0 | 239 | javaScript: 'javaScript', |
michael@0 | 240 | smask: 'smask', |
michael@0 | 241 | shadingPattern: 'shadingPattern', |
michael@0 | 242 | font: 'font' |
michael@0 | 243 | }; |
michael@0 | 244 | |
michael@0 | 245 | var UnsupportedManager = PDFJS.UnsupportedManager = |
michael@0 | 246 | (function UnsupportedManagerClosure() { |
michael@0 | 247 | var listeners = []; |
michael@0 | 248 | return { |
michael@0 | 249 | listen: function (cb) { |
michael@0 | 250 | listeners.push(cb); |
michael@0 | 251 | }, |
michael@0 | 252 | notify: function (featureId) { |
michael@0 | 253 | warn('Unsupported feature "' + featureId + '"'); |
michael@0 | 254 | for (var i = 0, ii = listeners.length; i < ii; i++) { |
michael@0 | 255 | listeners[i](featureId); |
michael@0 | 256 | } |
michael@0 | 257 | } |
michael@0 | 258 | }; |
michael@0 | 259 | })(); |
michael@0 | 260 | |
michael@0 | 261 | // Combines two URLs. The baseUrl shall be absolute URL. If the url is an |
michael@0 | 262 | // absolute URL, it will be returned as is. |
michael@0 | 263 | function combineUrl(baseUrl, url) { |
michael@0 | 264 | if (!url) { |
michael@0 | 265 | return baseUrl; |
michael@0 | 266 | } |
michael@0 | 267 | if (/^[a-z][a-z0-9+\-.]*:/i.test(url)) { |
michael@0 | 268 | return url; |
michael@0 | 269 | } |
michael@0 | 270 | var i; |
michael@0 | 271 | if (url.charAt(0) == '/') { |
michael@0 | 272 | // absolute path |
michael@0 | 273 | i = baseUrl.indexOf('://'); |
michael@0 | 274 | if (url.charAt(1) === '/') { |
michael@0 | 275 | ++i; |
michael@0 | 276 | } else { |
michael@0 | 277 | i = baseUrl.indexOf('/', i + 3); |
michael@0 | 278 | } |
michael@0 | 279 | return baseUrl.substring(0, i) + url; |
michael@0 | 280 | } else { |
michael@0 | 281 | // relative path |
michael@0 | 282 | var pathLength = baseUrl.length; |
michael@0 | 283 | i = baseUrl.lastIndexOf('#'); |
michael@0 | 284 | pathLength = i >= 0 ? i : pathLength; |
michael@0 | 285 | i = baseUrl.lastIndexOf('?', pathLength); |
michael@0 | 286 | pathLength = i >= 0 ? i : pathLength; |
michael@0 | 287 | var prefixLength = baseUrl.lastIndexOf('/', pathLength); |
michael@0 | 288 | return baseUrl.substring(0, prefixLength + 1) + url; |
michael@0 | 289 | } |
michael@0 | 290 | } |
michael@0 | 291 | |
michael@0 | 292 | // Validates if URL is safe and allowed, e.g. to avoid XSS. |
michael@0 | 293 | function isValidUrl(url, allowRelative) { |
michael@0 | 294 | if (!url) { |
michael@0 | 295 | return false; |
michael@0 | 296 | } |
michael@0 | 297 | // RFC 3986 (http://tools.ietf.org/html/rfc3986#section-3.1) |
michael@0 | 298 | // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) |
michael@0 | 299 | var protocol = /^[a-z][a-z0-9+\-.]*(?=:)/i.exec(url); |
michael@0 | 300 | if (!protocol) { |
michael@0 | 301 | return allowRelative; |
michael@0 | 302 | } |
michael@0 | 303 | protocol = protocol[0].toLowerCase(); |
michael@0 | 304 | switch (protocol) { |
michael@0 | 305 | case 'http': |
michael@0 | 306 | case 'https': |
michael@0 | 307 | case 'ftp': |
michael@0 | 308 | case 'mailto': |
michael@0 | 309 | return true; |
michael@0 | 310 | default: |
michael@0 | 311 | return false; |
michael@0 | 312 | } |
michael@0 | 313 | } |
michael@0 | 314 | PDFJS.isValidUrl = isValidUrl; |
michael@0 | 315 | |
michael@0 | 316 | function shadow(obj, prop, value) { |
michael@0 | 317 | Object.defineProperty(obj, prop, { value: value, |
michael@0 | 318 | enumerable: true, |
michael@0 | 319 | configurable: true, |
michael@0 | 320 | writable: false }); |
michael@0 | 321 | return value; |
michael@0 | 322 | } |
michael@0 | 323 | |
michael@0 | 324 | var PasswordResponses = PDFJS.PasswordResponses = { |
michael@0 | 325 | NEED_PASSWORD: 1, |
michael@0 | 326 | INCORRECT_PASSWORD: 2 |
michael@0 | 327 | }; |
michael@0 | 328 | |
michael@0 | 329 | var PasswordException = (function PasswordExceptionClosure() { |
michael@0 | 330 | function PasswordException(msg, code) { |
michael@0 | 331 | this.name = 'PasswordException'; |
michael@0 | 332 | this.message = msg; |
michael@0 | 333 | this.code = code; |
michael@0 | 334 | } |
michael@0 | 335 | |
michael@0 | 336 | PasswordException.prototype = new Error(); |
michael@0 | 337 | PasswordException.constructor = PasswordException; |
michael@0 | 338 | |
michael@0 | 339 | return PasswordException; |
michael@0 | 340 | })(); |
michael@0 | 341 | |
michael@0 | 342 | var UnknownErrorException = (function UnknownErrorExceptionClosure() { |
michael@0 | 343 | function UnknownErrorException(msg, details) { |
michael@0 | 344 | this.name = 'UnknownErrorException'; |
michael@0 | 345 | this.message = msg; |
michael@0 | 346 | this.details = details; |
michael@0 | 347 | } |
michael@0 | 348 | |
michael@0 | 349 | UnknownErrorException.prototype = new Error(); |
michael@0 | 350 | UnknownErrorException.constructor = UnknownErrorException; |
michael@0 | 351 | |
michael@0 | 352 | return UnknownErrorException; |
michael@0 | 353 | })(); |
michael@0 | 354 | |
michael@0 | 355 | var InvalidPDFException = (function InvalidPDFExceptionClosure() { |
michael@0 | 356 | function InvalidPDFException(msg) { |
michael@0 | 357 | this.name = 'InvalidPDFException'; |
michael@0 | 358 | this.message = msg; |
michael@0 | 359 | } |
michael@0 | 360 | |
michael@0 | 361 | InvalidPDFException.prototype = new Error(); |
michael@0 | 362 | InvalidPDFException.constructor = InvalidPDFException; |
michael@0 | 363 | |
michael@0 | 364 | return InvalidPDFException; |
michael@0 | 365 | })(); |
michael@0 | 366 | |
michael@0 | 367 | var MissingPDFException = (function MissingPDFExceptionClosure() { |
michael@0 | 368 | function MissingPDFException(msg) { |
michael@0 | 369 | this.name = 'MissingPDFException'; |
michael@0 | 370 | this.message = msg; |
michael@0 | 371 | } |
michael@0 | 372 | |
michael@0 | 373 | MissingPDFException.prototype = new Error(); |
michael@0 | 374 | MissingPDFException.constructor = MissingPDFException; |
michael@0 | 375 | |
michael@0 | 376 | return MissingPDFException; |
michael@0 | 377 | })(); |
michael@0 | 378 | |
michael@0 | 379 | var NotImplementedException = (function NotImplementedExceptionClosure() { |
michael@0 | 380 | function NotImplementedException(msg) { |
michael@0 | 381 | this.message = msg; |
michael@0 | 382 | } |
michael@0 | 383 | |
michael@0 | 384 | NotImplementedException.prototype = new Error(); |
michael@0 | 385 | NotImplementedException.prototype.name = 'NotImplementedException'; |
michael@0 | 386 | NotImplementedException.constructor = NotImplementedException; |
michael@0 | 387 | |
michael@0 | 388 | return NotImplementedException; |
michael@0 | 389 | })(); |
michael@0 | 390 | |
michael@0 | 391 | var MissingDataException = (function MissingDataExceptionClosure() { |
michael@0 | 392 | function MissingDataException(begin, end) { |
michael@0 | 393 | this.begin = begin; |
michael@0 | 394 | this.end = end; |
michael@0 | 395 | this.message = 'Missing data [' + begin + ', ' + end + ')'; |
michael@0 | 396 | } |
michael@0 | 397 | |
michael@0 | 398 | MissingDataException.prototype = new Error(); |
michael@0 | 399 | MissingDataException.prototype.name = 'MissingDataException'; |
michael@0 | 400 | MissingDataException.constructor = MissingDataException; |
michael@0 | 401 | |
michael@0 | 402 | return MissingDataException; |
michael@0 | 403 | })(); |
michael@0 | 404 | |
michael@0 | 405 | var XRefParseException = (function XRefParseExceptionClosure() { |
michael@0 | 406 | function XRefParseException(msg) { |
michael@0 | 407 | this.message = msg; |
michael@0 | 408 | } |
michael@0 | 409 | |
michael@0 | 410 | XRefParseException.prototype = new Error(); |
michael@0 | 411 | XRefParseException.prototype.name = 'XRefParseException'; |
michael@0 | 412 | XRefParseException.constructor = XRefParseException; |
michael@0 | 413 | |
michael@0 | 414 | return XRefParseException; |
michael@0 | 415 | })(); |
michael@0 | 416 | |
michael@0 | 417 | |
michael@0 | 418 | function bytesToString(bytes) { |
michael@0 | 419 | var length = bytes.length; |
michael@0 | 420 | var MAX_ARGUMENT_COUNT = 8192; |
michael@0 | 421 | if (length < MAX_ARGUMENT_COUNT) { |
michael@0 | 422 | return String.fromCharCode.apply(null, bytes); |
michael@0 | 423 | } |
michael@0 | 424 | var strBuf = []; |
michael@0 | 425 | for (var i = 0; i < length; i += MAX_ARGUMENT_COUNT) { |
michael@0 | 426 | var chunkEnd = Math.min(i + MAX_ARGUMENT_COUNT, length); |
michael@0 | 427 | var chunk = bytes.subarray(i, chunkEnd); |
michael@0 | 428 | strBuf.push(String.fromCharCode.apply(null, chunk)); |
michael@0 | 429 | } |
michael@0 | 430 | return strBuf.join(''); |
michael@0 | 431 | } |
michael@0 | 432 | |
michael@0 | 433 | function stringToArray(str) { |
michael@0 | 434 | var length = str.length; |
michael@0 | 435 | var array = []; |
michael@0 | 436 | for (var i = 0; i < length; ++i) { |
michael@0 | 437 | array[i] = str.charCodeAt(i); |
michael@0 | 438 | } |
michael@0 | 439 | return array; |
michael@0 | 440 | } |
michael@0 | 441 | |
michael@0 | 442 | function stringToBytes(str) { |
michael@0 | 443 | var length = str.length; |
michael@0 | 444 | var bytes = new Uint8Array(length); |
michael@0 | 445 | for (var i = 0; i < length; ++i) { |
michael@0 | 446 | bytes[i] = str.charCodeAt(i) & 0xFF; |
michael@0 | 447 | } |
michael@0 | 448 | return bytes; |
michael@0 | 449 | } |
michael@0 | 450 | |
michael@0 | 451 | function string32(value) { |
michael@0 | 452 | return String.fromCharCode((value >> 24) & 0xff, (value >> 16) & 0xff, |
michael@0 | 453 | (value >> 8) & 0xff, value & 0xff); |
michael@0 | 454 | } |
michael@0 | 455 | |
michael@0 | 456 | function log2(x) { |
michael@0 | 457 | var n = 1, i = 0; |
michael@0 | 458 | while (x > n) { |
michael@0 | 459 | n <<= 1; |
michael@0 | 460 | i++; |
michael@0 | 461 | } |
michael@0 | 462 | return i; |
michael@0 | 463 | } |
michael@0 | 464 | |
michael@0 | 465 | function readInt8(data, start) { |
michael@0 | 466 | return (data[start] << 24) >> 24; |
michael@0 | 467 | } |
michael@0 | 468 | |
michael@0 | 469 | function readUint16(data, offset) { |
michael@0 | 470 | return (data[offset] << 8) | data[offset + 1]; |
michael@0 | 471 | } |
michael@0 | 472 | |
michael@0 | 473 | function readUint32(data, offset) { |
michael@0 | 474 | return ((data[offset] << 24) | (data[offset + 1] << 16) | |
michael@0 | 475 | (data[offset + 2] << 8) | data[offset + 3]) >>> 0; |
michael@0 | 476 | } |
michael@0 | 477 | |
michael@0 | 478 | // Lazy test the endianness of the platform |
michael@0 | 479 | // NOTE: This will be 'true' for simulated TypedArrays |
michael@0 | 480 | function isLittleEndian() { |
michael@0 | 481 | var buffer8 = new Uint8Array(2); |
michael@0 | 482 | buffer8[0] = 1; |
michael@0 | 483 | var buffer16 = new Uint16Array(buffer8.buffer); |
michael@0 | 484 | return (buffer16[0] === 1); |
michael@0 | 485 | } |
michael@0 | 486 | |
michael@0 | 487 | Object.defineProperty(PDFJS, 'isLittleEndian', { |
michael@0 | 488 | configurable: true, |
michael@0 | 489 | get: function PDFJS_isLittleEndian() { |
michael@0 | 490 | return shadow(PDFJS, 'isLittleEndian', isLittleEndian()); |
michael@0 | 491 | } |
michael@0 | 492 | }); |
michael@0 | 493 | |
michael@0 | 494 | PDFJS.hasCanvasTypedArrays = true; |
michael@0 | 495 | |
michael@0 | 496 | var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0]; |
michael@0 | 497 | |
michael@0 | 498 | var Util = PDFJS.Util = (function UtilClosure() { |
michael@0 | 499 | function Util() {} |
michael@0 | 500 | |
michael@0 | 501 | Util.makeCssRgb = function Util_makeCssRgb(rgb) { |
michael@0 | 502 | return 'rgb(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ')'; |
michael@0 | 503 | }; |
michael@0 | 504 | |
michael@0 | 505 | Util.makeCssCmyk = function Util_makeCssCmyk(cmyk) { |
michael@0 | 506 | var rgb = ColorSpace.singletons.cmyk.getRgb(cmyk, 0); |
michael@0 | 507 | return Util.makeCssRgb(rgb); |
michael@0 | 508 | }; |
michael@0 | 509 | |
michael@0 | 510 | // Concatenates two transformation matrices together and returns the result. |
michael@0 | 511 | Util.transform = function Util_transform(m1, m2) { |
michael@0 | 512 | return [ |
michael@0 | 513 | m1[0] * m2[0] + m1[2] * m2[1], |
michael@0 | 514 | m1[1] * m2[0] + m1[3] * m2[1], |
michael@0 | 515 | m1[0] * m2[2] + m1[2] * m2[3], |
michael@0 | 516 | m1[1] * m2[2] + m1[3] * m2[3], |
michael@0 | 517 | m1[0] * m2[4] + m1[2] * m2[5] + m1[4], |
michael@0 | 518 | m1[1] * m2[4] + m1[3] * m2[5] + m1[5] |
michael@0 | 519 | ]; |
michael@0 | 520 | }; |
michael@0 | 521 | |
michael@0 | 522 | // For 2d affine transforms |
michael@0 | 523 | Util.applyTransform = function Util_applyTransform(p, m) { |
michael@0 | 524 | var xt = p[0] * m[0] + p[1] * m[2] + m[4]; |
michael@0 | 525 | var yt = p[0] * m[1] + p[1] * m[3] + m[5]; |
michael@0 | 526 | return [xt, yt]; |
michael@0 | 527 | }; |
michael@0 | 528 | |
michael@0 | 529 | Util.applyInverseTransform = function Util_applyInverseTransform(p, m) { |
michael@0 | 530 | var d = m[0] * m[3] - m[1] * m[2]; |
michael@0 | 531 | var xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d; |
michael@0 | 532 | var yt = (-p[0] * m[1] + p[1] * m[0] + m[4] * m[1] - m[5] * m[0]) / d; |
michael@0 | 533 | return [xt, yt]; |
michael@0 | 534 | }; |
michael@0 | 535 | |
michael@0 | 536 | // Applies the transform to the rectangle and finds the minimum axially |
michael@0 | 537 | // aligned bounding box. |
michael@0 | 538 | Util.getAxialAlignedBoundingBox = |
michael@0 | 539 | function Util_getAxialAlignedBoundingBox(r, m) { |
michael@0 | 540 | |
michael@0 | 541 | var p1 = Util.applyTransform(r, m); |
michael@0 | 542 | var p2 = Util.applyTransform(r.slice(2, 4), m); |
michael@0 | 543 | var p3 = Util.applyTransform([r[0], r[3]], m); |
michael@0 | 544 | var p4 = Util.applyTransform([r[2], r[1]], m); |
michael@0 | 545 | return [ |
michael@0 | 546 | Math.min(p1[0], p2[0], p3[0], p4[0]), |
michael@0 | 547 | Math.min(p1[1], p2[1], p3[1], p4[1]), |
michael@0 | 548 | Math.max(p1[0], p2[0], p3[0], p4[0]), |
michael@0 | 549 | Math.max(p1[1], p2[1], p3[1], p4[1]) |
michael@0 | 550 | ]; |
michael@0 | 551 | }; |
michael@0 | 552 | |
michael@0 | 553 | Util.inverseTransform = function Util_inverseTransform(m) { |
michael@0 | 554 | var d = m[0] * m[3] - m[1] * m[2]; |
michael@0 | 555 | return [m[3] / d, -m[1] / d, -m[2] / d, m[0] / d, |
michael@0 | 556 | (m[2] * m[5] - m[4] * m[3]) / d, (m[4] * m[1] - m[5] * m[0]) / d]; |
michael@0 | 557 | }; |
michael@0 | 558 | |
michael@0 | 559 | // Apply a generic 3d matrix M on a 3-vector v: |
michael@0 | 560 | // | a b c | | X | |
michael@0 | 561 | // | d e f | x | Y | |
michael@0 | 562 | // | g h i | | Z | |
michael@0 | 563 | // M is assumed to be serialized as [a,b,c,d,e,f,g,h,i], |
michael@0 | 564 | // with v as [X,Y,Z] |
michael@0 | 565 | Util.apply3dTransform = function Util_apply3dTransform(m, v) { |
michael@0 | 566 | return [ |
michael@0 | 567 | m[0] * v[0] + m[1] * v[1] + m[2] * v[2], |
michael@0 | 568 | m[3] * v[0] + m[4] * v[1] + m[5] * v[2], |
michael@0 | 569 | m[6] * v[0] + m[7] * v[1] + m[8] * v[2] |
michael@0 | 570 | ]; |
michael@0 | 571 | }; |
michael@0 | 572 | |
michael@0 | 573 | // This calculation uses Singular Value Decomposition. |
michael@0 | 574 | // The SVD can be represented with formula A = USV. We are interested in the |
michael@0 | 575 | // matrix S here because it represents the scale values. |
michael@0 | 576 | Util.singularValueDecompose2dScale = |
michael@0 | 577 | function Util_singularValueDecompose2dScale(m) { |
michael@0 | 578 | |
michael@0 | 579 | var transpose = [m[0], m[2], m[1], m[3]]; |
michael@0 | 580 | |
michael@0 | 581 | // Multiply matrix m with its transpose. |
michael@0 | 582 | var a = m[0] * transpose[0] + m[1] * transpose[2]; |
michael@0 | 583 | var b = m[0] * transpose[1] + m[1] * transpose[3]; |
michael@0 | 584 | var c = m[2] * transpose[0] + m[3] * transpose[2]; |
michael@0 | 585 | var d = m[2] * transpose[1] + m[3] * transpose[3]; |
michael@0 | 586 | |
michael@0 | 587 | // Solve the second degree polynomial to get roots. |
michael@0 | 588 | var first = (a + d) / 2; |
michael@0 | 589 | var second = Math.sqrt((a + d) * (a + d) - 4 * (a * d - c * b)) / 2; |
michael@0 | 590 | var sx = first + second || 1; |
michael@0 | 591 | var sy = first - second || 1; |
michael@0 | 592 | |
michael@0 | 593 | // Scale values are the square roots of the eigenvalues. |
michael@0 | 594 | return [Math.sqrt(sx), Math.sqrt(sy)]; |
michael@0 | 595 | }; |
michael@0 | 596 | |
michael@0 | 597 | // Normalize rectangle rect=[x1, y1, x2, y2] so that (x1,y1) < (x2,y2) |
michael@0 | 598 | // For coordinate systems whose origin lies in the bottom-left, this |
michael@0 | 599 | // means normalization to (BL,TR) ordering. For systems with origin in the |
michael@0 | 600 | // top-left, this means (TL,BR) ordering. |
michael@0 | 601 | Util.normalizeRect = function Util_normalizeRect(rect) { |
michael@0 | 602 | var r = rect.slice(0); // clone rect |
michael@0 | 603 | if (rect[0] > rect[2]) { |
michael@0 | 604 | r[0] = rect[2]; |
michael@0 | 605 | r[2] = rect[0]; |
michael@0 | 606 | } |
michael@0 | 607 | if (rect[1] > rect[3]) { |
michael@0 | 608 | r[1] = rect[3]; |
michael@0 | 609 | r[3] = rect[1]; |
michael@0 | 610 | } |
michael@0 | 611 | return r; |
michael@0 | 612 | }; |
michael@0 | 613 | |
michael@0 | 614 | // Returns a rectangle [x1, y1, x2, y2] corresponding to the |
michael@0 | 615 | // intersection of rect1 and rect2. If no intersection, returns 'false' |
michael@0 | 616 | // The rectangle coordinates of rect1, rect2 should be [x1, y1, x2, y2] |
michael@0 | 617 | Util.intersect = function Util_intersect(rect1, rect2) { |
michael@0 | 618 | function compare(a, b) { |
michael@0 | 619 | return a - b; |
michael@0 | 620 | } |
michael@0 | 621 | |
michael@0 | 622 | // Order points along the axes |
michael@0 | 623 | var orderedX = [rect1[0], rect1[2], rect2[0], rect2[2]].sort(compare), |
michael@0 | 624 | orderedY = [rect1[1], rect1[3], rect2[1], rect2[3]].sort(compare), |
michael@0 | 625 | result = []; |
michael@0 | 626 | |
michael@0 | 627 | rect1 = Util.normalizeRect(rect1); |
michael@0 | 628 | rect2 = Util.normalizeRect(rect2); |
michael@0 | 629 | |
michael@0 | 630 | // X: first and second points belong to different rectangles? |
michael@0 | 631 | if ((orderedX[0] === rect1[0] && orderedX[1] === rect2[0]) || |
michael@0 | 632 | (orderedX[0] === rect2[0] && orderedX[1] === rect1[0])) { |
michael@0 | 633 | // Intersection must be between second and third points |
michael@0 | 634 | result[0] = orderedX[1]; |
michael@0 | 635 | result[2] = orderedX[2]; |
michael@0 | 636 | } else { |
michael@0 | 637 | return false; |
michael@0 | 638 | } |
michael@0 | 639 | |
michael@0 | 640 | // Y: first and second points belong to different rectangles? |
michael@0 | 641 | if ((orderedY[0] === rect1[1] && orderedY[1] === rect2[1]) || |
michael@0 | 642 | (orderedY[0] === rect2[1] && orderedY[1] === rect1[1])) { |
michael@0 | 643 | // Intersection must be between second and third points |
michael@0 | 644 | result[1] = orderedY[1]; |
michael@0 | 645 | result[3] = orderedY[2]; |
michael@0 | 646 | } else { |
michael@0 | 647 | return false; |
michael@0 | 648 | } |
michael@0 | 649 | |
michael@0 | 650 | return result; |
michael@0 | 651 | }; |
michael@0 | 652 | |
michael@0 | 653 | Util.sign = function Util_sign(num) { |
michael@0 | 654 | return num < 0 ? -1 : 1; |
michael@0 | 655 | }; |
michael@0 | 656 | |
michael@0 | 657 | // TODO(mack): Rename appendToArray |
michael@0 | 658 | Util.concatenateToArray = function concatenateToArray(arr1, arr2) { |
michael@0 | 659 | Array.prototype.push.apply(arr1, arr2); |
michael@0 | 660 | }; |
michael@0 | 661 | |
michael@0 | 662 | Util.prependToArray = function concatenateToArray(arr1, arr2) { |
michael@0 | 663 | Array.prototype.unshift.apply(arr1, arr2); |
michael@0 | 664 | }; |
michael@0 | 665 | |
michael@0 | 666 | Util.extendObj = function extendObj(obj1, obj2) { |
michael@0 | 667 | for (var key in obj2) { |
michael@0 | 668 | obj1[key] = obj2[key]; |
michael@0 | 669 | } |
michael@0 | 670 | }; |
michael@0 | 671 | |
michael@0 | 672 | Util.getInheritableProperty = function Util_getInheritableProperty(dict, |
michael@0 | 673 | name) { |
michael@0 | 674 | while (dict && !dict.has(name)) { |
michael@0 | 675 | dict = dict.get('Parent'); |
michael@0 | 676 | } |
michael@0 | 677 | if (!dict) { |
michael@0 | 678 | return null; |
michael@0 | 679 | } |
michael@0 | 680 | return dict.get(name); |
michael@0 | 681 | }; |
michael@0 | 682 | |
michael@0 | 683 | Util.inherit = function Util_inherit(sub, base, prototype) { |
michael@0 | 684 | sub.prototype = Object.create(base.prototype); |
michael@0 | 685 | sub.prototype.constructor = sub; |
michael@0 | 686 | for (var prop in prototype) { |
michael@0 | 687 | sub.prototype[prop] = prototype[prop]; |
michael@0 | 688 | } |
michael@0 | 689 | }; |
michael@0 | 690 | |
michael@0 | 691 | Util.loadScript = function Util_loadScript(src, callback) { |
michael@0 | 692 | var script = document.createElement('script'); |
michael@0 | 693 | var loaded = false; |
michael@0 | 694 | script.setAttribute('src', src); |
michael@0 | 695 | if (callback) { |
michael@0 | 696 | script.onload = function() { |
michael@0 | 697 | if (!loaded) { |
michael@0 | 698 | callback(); |
michael@0 | 699 | } |
michael@0 | 700 | loaded = true; |
michael@0 | 701 | }; |
michael@0 | 702 | } |
michael@0 | 703 | document.getElementsByTagName('head')[0].appendChild(script); |
michael@0 | 704 | }; |
michael@0 | 705 | |
michael@0 | 706 | return Util; |
michael@0 | 707 | })(); |
michael@0 | 708 | |
michael@0 | 709 | var PageViewport = PDFJS.PageViewport = (function PageViewportClosure() { |
michael@0 | 710 | function PageViewport(viewBox, scale, rotation, offsetX, offsetY, dontFlip) { |
michael@0 | 711 | this.viewBox = viewBox; |
michael@0 | 712 | this.scale = scale; |
michael@0 | 713 | this.rotation = rotation; |
michael@0 | 714 | this.offsetX = offsetX; |
michael@0 | 715 | this.offsetY = offsetY; |
michael@0 | 716 | |
michael@0 | 717 | // creating transform to convert pdf coordinate system to the normal |
michael@0 | 718 | // canvas like coordinates taking in account scale and rotation |
michael@0 | 719 | var centerX = (viewBox[2] + viewBox[0]) / 2; |
michael@0 | 720 | var centerY = (viewBox[3] + viewBox[1]) / 2; |
michael@0 | 721 | var rotateA, rotateB, rotateC, rotateD; |
michael@0 | 722 | rotation = rotation % 360; |
michael@0 | 723 | rotation = rotation < 0 ? rotation + 360 : rotation; |
michael@0 | 724 | switch (rotation) { |
michael@0 | 725 | case 180: |
michael@0 | 726 | rotateA = -1; rotateB = 0; rotateC = 0; rotateD = 1; |
michael@0 | 727 | break; |
michael@0 | 728 | case 90: |
michael@0 | 729 | rotateA = 0; rotateB = 1; rotateC = 1; rotateD = 0; |
michael@0 | 730 | break; |
michael@0 | 731 | case 270: |
michael@0 | 732 | rotateA = 0; rotateB = -1; rotateC = -1; rotateD = 0; |
michael@0 | 733 | break; |
michael@0 | 734 | //case 0: |
michael@0 | 735 | default: |
michael@0 | 736 | rotateA = 1; rotateB = 0; rotateC = 0; rotateD = -1; |
michael@0 | 737 | break; |
michael@0 | 738 | } |
michael@0 | 739 | |
michael@0 | 740 | if (dontFlip) { |
michael@0 | 741 | rotateC = -rotateC; rotateD = -rotateD; |
michael@0 | 742 | } |
michael@0 | 743 | |
michael@0 | 744 | var offsetCanvasX, offsetCanvasY; |
michael@0 | 745 | var width, height; |
michael@0 | 746 | if (rotateA === 0) { |
michael@0 | 747 | offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX; |
michael@0 | 748 | offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY; |
michael@0 | 749 | width = Math.abs(viewBox[3] - viewBox[1]) * scale; |
michael@0 | 750 | height = Math.abs(viewBox[2] - viewBox[0]) * scale; |
michael@0 | 751 | } else { |
michael@0 | 752 | offsetCanvasX = Math.abs(centerX - viewBox[0]) * scale + offsetX; |
michael@0 | 753 | offsetCanvasY = Math.abs(centerY - viewBox[1]) * scale + offsetY; |
michael@0 | 754 | width = Math.abs(viewBox[2] - viewBox[0]) * scale; |
michael@0 | 755 | height = Math.abs(viewBox[3] - viewBox[1]) * scale; |
michael@0 | 756 | } |
michael@0 | 757 | // creating transform for the following operations: |
michael@0 | 758 | // translate(-centerX, -centerY), rotate and flip vertically, |
michael@0 | 759 | // scale, and translate(offsetCanvasX, offsetCanvasY) |
michael@0 | 760 | this.transform = [ |
michael@0 | 761 | rotateA * scale, |
michael@0 | 762 | rotateB * scale, |
michael@0 | 763 | rotateC * scale, |
michael@0 | 764 | rotateD * scale, |
michael@0 | 765 | offsetCanvasX - rotateA * scale * centerX - rotateC * scale * centerY, |
michael@0 | 766 | offsetCanvasY - rotateB * scale * centerX - rotateD * scale * centerY |
michael@0 | 767 | ]; |
michael@0 | 768 | |
michael@0 | 769 | this.width = width; |
michael@0 | 770 | this.height = height; |
michael@0 | 771 | this.fontScale = scale; |
michael@0 | 772 | } |
michael@0 | 773 | PageViewport.prototype = { |
michael@0 | 774 | clone: function PageViewPort_clone(args) { |
michael@0 | 775 | args = args || {}; |
michael@0 | 776 | var scale = 'scale' in args ? args.scale : this.scale; |
michael@0 | 777 | var rotation = 'rotation' in args ? args.rotation : this.rotation; |
michael@0 | 778 | return new PageViewport(this.viewBox.slice(), scale, rotation, |
michael@0 | 779 | this.offsetX, this.offsetY, args.dontFlip); |
michael@0 | 780 | }, |
michael@0 | 781 | convertToViewportPoint: function PageViewport_convertToViewportPoint(x, y) { |
michael@0 | 782 | return Util.applyTransform([x, y], this.transform); |
michael@0 | 783 | }, |
michael@0 | 784 | convertToViewportRectangle: |
michael@0 | 785 | function PageViewport_convertToViewportRectangle(rect) { |
michael@0 | 786 | var tl = Util.applyTransform([rect[0], rect[1]], this.transform); |
michael@0 | 787 | var br = Util.applyTransform([rect[2], rect[3]], this.transform); |
michael@0 | 788 | return [tl[0], tl[1], br[0], br[1]]; |
michael@0 | 789 | }, |
michael@0 | 790 | convertToPdfPoint: function PageViewport_convertToPdfPoint(x, y) { |
michael@0 | 791 | return Util.applyInverseTransform([x, y], this.transform); |
michael@0 | 792 | } |
michael@0 | 793 | }; |
michael@0 | 794 | return PageViewport; |
michael@0 | 795 | })(); |
michael@0 | 796 | |
michael@0 | 797 | var PDFStringTranslateTable = [ |
michael@0 | 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, |
michael@0 | 799 | 0x2D8, 0x2C7, 0x2C6, 0x2D9, 0x2DD, 0x2DB, 0x2DA, 0x2DC, 0, 0, 0, 0, 0, 0, 0, |
michael@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, |
michael@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, |
michael@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, |
michael@0 | 803 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2022, 0x2020, 0x2021, 0x2026, 0x2014, |
michael@0 | 804 | 0x2013, 0x192, 0x2044, 0x2039, 0x203A, 0x2212, 0x2030, 0x201E, 0x201C, |
michael@0 | 805 | 0x201D, 0x2018, 0x2019, 0x201A, 0x2122, 0xFB01, 0xFB02, 0x141, 0x152, 0x160, |
michael@0 | 806 | 0x178, 0x17D, 0x131, 0x142, 0x153, 0x161, 0x17E, 0, 0x20AC |
michael@0 | 807 | ]; |
michael@0 | 808 | |
michael@0 | 809 | function stringToPDFString(str) { |
michael@0 | 810 | var i, n = str.length, strBuf = []; |
michael@0 | 811 | if (str[0] === '\xFE' && str[1] === '\xFF') { |
michael@0 | 812 | // UTF16BE BOM |
michael@0 | 813 | for (i = 2; i < n; i += 2) { |
michael@0 | 814 | strBuf.push(String.fromCharCode( |
michael@0 | 815 | (str.charCodeAt(i) << 8) | str.charCodeAt(i + 1))); |
michael@0 | 816 | } |
michael@0 | 817 | } else { |
michael@0 | 818 | for (i = 0; i < n; ++i) { |
michael@0 | 819 | var code = PDFStringTranslateTable[str.charCodeAt(i)]; |
michael@0 | 820 | strBuf.push(code ? String.fromCharCode(code) : str.charAt(i)); |
michael@0 | 821 | } |
michael@0 | 822 | } |
michael@0 | 823 | return strBuf.join(''); |
michael@0 | 824 | } |
michael@0 | 825 | |
michael@0 | 826 | function stringToUTF8String(str) { |
michael@0 | 827 | return decodeURIComponent(escape(str)); |
michael@0 | 828 | } |
michael@0 | 829 | |
michael@0 | 830 | function isEmptyObj(obj) { |
michael@0 | 831 | for (var key in obj) { |
michael@0 | 832 | return false; |
michael@0 | 833 | } |
michael@0 | 834 | return true; |
michael@0 | 835 | } |
michael@0 | 836 | |
michael@0 | 837 | function isBool(v) { |
michael@0 | 838 | return typeof v == 'boolean'; |
michael@0 | 839 | } |
michael@0 | 840 | |
michael@0 | 841 | function isInt(v) { |
michael@0 | 842 | return typeof v == 'number' && ((v | 0) == v); |
michael@0 | 843 | } |
michael@0 | 844 | |
michael@0 | 845 | function isNum(v) { |
michael@0 | 846 | return typeof v == 'number'; |
michael@0 | 847 | } |
michael@0 | 848 | |
michael@0 | 849 | function isString(v) { |
michael@0 | 850 | return typeof v == 'string'; |
michael@0 | 851 | } |
michael@0 | 852 | |
michael@0 | 853 | function isNull(v) { |
michael@0 | 854 | return v === null; |
michael@0 | 855 | } |
michael@0 | 856 | |
michael@0 | 857 | function isName(v) { |
michael@0 | 858 | return v instanceof Name; |
michael@0 | 859 | } |
michael@0 | 860 | |
michael@0 | 861 | function isCmd(v, cmd) { |
michael@0 | 862 | return v instanceof Cmd && (!cmd || v.cmd == cmd); |
michael@0 | 863 | } |
michael@0 | 864 | |
michael@0 | 865 | function isDict(v, type) { |
michael@0 | 866 | if (!(v instanceof Dict)) { |
michael@0 | 867 | return false; |
michael@0 | 868 | } |
michael@0 | 869 | if (!type) { |
michael@0 | 870 | return true; |
michael@0 | 871 | } |
michael@0 | 872 | var dictType = v.get('Type'); |
michael@0 | 873 | return isName(dictType) && dictType.name == type; |
michael@0 | 874 | } |
michael@0 | 875 | |
michael@0 | 876 | function isArray(v) { |
michael@0 | 877 | return v instanceof Array; |
michael@0 | 878 | } |
michael@0 | 879 | |
michael@0 | 880 | function isStream(v) { |
michael@0 | 881 | return typeof v == 'object' && v !== null && v !== undefined && |
michael@0 | 882 | ('getBytes' in v); |
michael@0 | 883 | } |
michael@0 | 884 | |
michael@0 | 885 | function isArrayBuffer(v) { |
michael@0 | 886 | return typeof v == 'object' && v !== null && v !== undefined && |
michael@0 | 887 | ('byteLength' in v); |
michael@0 | 888 | } |
michael@0 | 889 | |
michael@0 | 890 | function isRef(v) { |
michael@0 | 891 | return v instanceof Ref; |
michael@0 | 892 | } |
michael@0 | 893 | |
michael@0 | 894 | function isPDFFunction(v) { |
michael@0 | 895 | var fnDict; |
michael@0 | 896 | if (typeof v != 'object') { |
michael@0 | 897 | return false; |
michael@0 | 898 | } else if (isDict(v)) { |
michael@0 | 899 | fnDict = v; |
michael@0 | 900 | } else if (isStream(v)) { |
michael@0 | 901 | fnDict = v.dict; |
michael@0 | 902 | } else { |
michael@0 | 903 | return false; |
michael@0 | 904 | } |
michael@0 | 905 | return fnDict.has('FunctionType'); |
michael@0 | 906 | } |
michael@0 | 907 | |
michael@0 | 908 | /** |
michael@0 | 909 | * Legacy support for PDFJS Promise implementation. |
michael@0 | 910 | * TODO remove eventually |
michael@0 | 911 | * @ignore |
michael@0 | 912 | */ |
michael@0 | 913 | var LegacyPromise = PDFJS.LegacyPromise = (function LegacyPromiseClosure() { |
michael@0 | 914 | return function LegacyPromise() { |
michael@0 | 915 | var resolve, reject; |
michael@0 | 916 | var promise = new Promise(function (resolve_, reject_) { |
michael@0 | 917 | resolve = resolve_; |
michael@0 | 918 | reject = reject_; |
michael@0 | 919 | }); |
michael@0 | 920 | promise.resolve = resolve; |
michael@0 | 921 | promise.reject = reject; |
michael@0 | 922 | return promise; |
michael@0 | 923 | }; |
michael@0 | 924 | })(); |
michael@0 | 925 | |
michael@0 | 926 | /** |
michael@0 | 927 | * Polyfill for Promises: |
michael@0 | 928 | * The following promise implementation tries to generally implment the |
michael@0 | 929 | * Promise/A+ spec. Some notable differences from other promise libaries are: |
michael@0 | 930 | * - There currently isn't a seperate deferred and promise object. |
michael@0 | 931 | * - Unhandled rejections eventually show an error if they aren't handled. |
michael@0 | 932 | * |
michael@0 | 933 | * Based off of the work in: |
michael@0 | 934 | * https://bugzilla.mozilla.org/show_bug.cgi?id=810490 |
michael@0 | 935 | */ |
michael@0 | 936 | (function PromiseClosure() { |
michael@0 | 937 | if (globalScope.Promise) { |
michael@0 | 938 | // Promises existing in the DOM/Worker, checking presence of all/resolve |
michael@0 | 939 | if (typeof globalScope.Promise.all !== 'function') { |
michael@0 | 940 | globalScope.Promise.all = function (iterable) { |
michael@0 | 941 | var count = 0, results = [], resolve, reject; |
michael@0 | 942 | var promise = new globalScope.Promise(function (resolve_, reject_) { |
michael@0 | 943 | resolve = resolve_; |
michael@0 | 944 | reject = reject_; |
michael@0 | 945 | }); |
michael@0 | 946 | iterable.forEach(function (p, i) { |
michael@0 | 947 | count++; |
michael@0 | 948 | p.then(function (result) { |
michael@0 | 949 | results[i] = result; |
michael@0 | 950 | count--; |
michael@0 | 951 | if (count === 0) { |
michael@0 | 952 | resolve(results); |
michael@0 | 953 | } |
michael@0 | 954 | }, reject); |
michael@0 | 955 | }); |
michael@0 | 956 | if (count === 0) { |
michael@0 | 957 | resolve(results); |
michael@0 | 958 | } |
michael@0 | 959 | return promise; |
michael@0 | 960 | }; |
michael@0 | 961 | } |
michael@0 | 962 | if (typeof globalScope.Promise.resolve !== 'function') { |
michael@0 | 963 | globalScope.Promise.resolve = function (x) { |
michael@0 | 964 | return new globalScope.Promise(function (resolve) { resolve(x); }); |
michael@0 | 965 | }; |
michael@0 | 966 | } |
michael@0 | 967 | return; |
michael@0 | 968 | } |
michael@0 | 969 | throw new Error('DOM Promise is not present'); |
michael@0 | 970 | })(); |
michael@0 | 971 | |
michael@0 | 972 | var StatTimer = (function StatTimerClosure() { |
michael@0 | 973 | function rpad(str, pad, length) { |
michael@0 | 974 | while (str.length < length) { |
michael@0 | 975 | str += pad; |
michael@0 | 976 | } |
michael@0 | 977 | return str; |
michael@0 | 978 | } |
michael@0 | 979 | function StatTimer() { |
michael@0 | 980 | this.started = {}; |
michael@0 | 981 | this.times = []; |
michael@0 | 982 | this.enabled = true; |
michael@0 | 983 | } |
michael@0 | 984 | StatTimer.prototype = { |
michael@0 | 985 | time: function StatTimer_time(name) { |
michael@0 | 986 | if (!this.enabled) { |
michael@0 | 987 | return; |
michael@0 | 988 | } |
michael@0 | 989 | if (name in this.started) { |
michael@0 | 990 | warn('Timer is already running for ' + name); |
michael@0 | 991 | } |
michael@0 | 992 | this.started[name] = Date.now(); |
michael@0 | 993 | }, |
michael@0 | 994 | timeEnd: function StatTimer_timeEnd(name) { |
michael@0 | 995 | if (!this.enabled) { |
michael@0 | 996 | return; |
michael@0 | 997 | } |
michael@0 | 998 | if (!(name in this.started)) { |
michael@0 | 999 | warn('Timer has not been started for ' + name); |
michael@0 | 1000 | } |
michael@0 | 1001 | this.times.push({ |
michael@0 | 1002 | 'name': name, |
michael@0 | 1003 | 'start': this.started[name], |
michael@0 | 1004 | 'end': Date.now() |
michael@0 | 1005 | }); |
michael@0 | 1006 | // Remove timer from started so it can be called again. |
michael@0 | 1007 | delete this.started[name]; |
michael@0 | 1008 | }, |
michael@0 | 1009 | toString: function StatTimer_toString() { |
michael@0 | 1010 | var i, ii; |
michael@0 | 1011 | var times = this.times; |
michael@0 | 1012 | var out = ''; |
michael@0 | 1013 | // Find the longest name for padding purposes. |
michael@0 | 1014 | var longest = 0; |
michael@0 | 1015 | for (i = 0, ii = times.length; i < ii; ++i) { |
michael@0 | 1016 | var name = times[i]['name']; |
michael@0 | 1017 | if (name.length > longest) { |
michael@0 | 1018 | longest = name.length; |
michael@0 | 1019 | } |
michael@0 | 1020 | } |
michael@0 | 1021 | for (i = 0, ii = times.length; i < ii; ++i) { |
michael@0 | 1022 | var span = times[i]; |
michael@0 | 1023 | var duration = span.end - span.start; |
michael@0 | 1024 | out += rpad(span['name'], ' ', longest) + ' ' + duration + 'ms\n'; |
michael@0 | 1025 | } |
michael@0 | 1026 | return out; |
michael@0 | 1027 | } |
michael@0 | 1028 | }; |
michael@0 | 1029 | return StatTimer; |
michael@0 | 1030 | })(); |
michael@0 | 1031 | |
michael@0 | 1032 | PDFJS.createBlob = function createBlob(data, contentType) { |
michael@0 | 1033 | if (typeof Blob !== 'undefined') { |
michael@0 | 1034 | return new Blob([data], { type: contentType }); |
michael@0 | 1035 | } |
michael@0 | 1036 | // Blob builder is deprecated in FF14 and removed in FF18. |
michael@0 | 1037 | var bb = new MozBlobBuilder(); |
michael@0 | 1038 | bb.append(data); |
michael@0 | 1039 | return bb.getBlob(contentType); |
michael@0 | 1040 | }; |
michael@0 | 1041 | |
michael@0 | 1042 | PDFJS.createObjectURL = (function createObjectURLClosure() { |
michael@0 | 1043 | // Blob/createObjectURL is not available, falling back to data schema. |
michael@0 | 1044 | var digits = |
michael@0 | 1045 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; |
michael@0 | 1046 | |
michael@0 | 1047 | return function createObjectURL(data, contentType) { |
michael@0 | 1048 | if (!PDFJS.disableCreateObjectURL && |
michael@0 | 1049 | typeof URL !== 'undefined' && URL.createObjectURL) { |
michael@0 | 1050 | var blob = PDFJS.createBlob(data, contentType); |
michael@0 | 1051 | return URL.createObjectURL(blob); |
michael@0 | 1052 | } |
michael@0 | 1053 | |
michael@0 | 1054 | var buffer = 'data:' + contentType + ';base64,'; |
michael@0 | 1055 | for (var i = 0, ii = data.length; i < ii; i += 3) { |
michael@0 | 1056 | var b1 = data[i] & 0xFF; |
michael@0 | 1057 | var b2 = data[i + 1] & 0xFF; |
michael@0 | 1058 | var b3 = data[i + 2] & 0xFF; |
michael@0 | 1059 | var d1 = b1 >> 2, d2 = ((b1 & 3) << 4) | (b2 >> 4); |
michael@0 | 1060 | var d3 = i + 1 < ii ? ((b2 & 0xF) << 2) | (b3 >> 6) : 64; |
michael@0 | 1061 | var d4 = i + 2 < ii ? (b3 & 0x3F) : 64; |
michael@0 | 1062 | buffer += digits[d1] + digits[d2] + digits[d3] + digits[d4]; |
michael@0 | 1063 | } |
michael@0 | 1064 | return buffer; |
michael@0 | 1065 | }; |
michael@0 | 1066 | })(); |
michael@0 | 1067 | |
michael@0 | 1068 | function MessageHandler(name, comObj) { |
michael@0 | 1069 | this.name = name; |
michael@0 | 1070 | this.comObj = comObj; |
michael@0 | 1071 | this.callbackIndex = 1; |
michael@0 | 1072 | this.postMessageTransfers = true; |
michael@0 | 1073 | var callbacks = this.callbacks = {}; |
michael@0 | 1074 | var ah = this.actionHandler = {}; |
michael@0 | 1075 | |
michael@0 | 1076 | ah['console_log'] = [function ahConsoleLog(data) { |
michael@0 | 1077 | console.log.apply(console, data); |
michael@0 | 1078 | }]; |
michael@0 | 1079 | ah['console_error'] = [function ahConsoleError(data) { |
michael@0 | 1080 | console.error.apply(console, data); |
michael@0 | 1081 | }]; |
michael@0 | 1082 | ah['_unsupported_feature'] = [function ah_unsupportedFeature(data) { |
michael@0 | 1083 | UnsupportedManager.notify(data); |
michael@0 | 1084 | }]; |
michael@0 | 1085 | |
michael@0 | 1086 | comObj.onmessage = function messageHandlerComObjOnMessage(event) { |
michael@0 | 1087 | var data = event.data; |
michael@0 | 1088 | if (data.isReply) { |
michael@0 | 1089 | var callbackId = data.callbackId; |
michael@0 | 1090 | if (data.callbackId in callbacks) { |
michael@0 | 1091 | var callback = callbacks[callbackId]; |
michael@0 | 1092 | delete callbacks[callbackId]; |
michael@0 | 1093 | callback(data.data); |
michael@0 | 1094 | } else { |
michael@0 | 1095 | error('Cannot resolve callback ' + callbackId); |
michael@0 | 1096 | } |
michael@0 | 1097 | } else if (data.action in ah) { |
michael@0 | 1098 | var action = ah[data.action]; |
michael@0 | 1099 | if (data.callbackId) { |
michael@0 | 1100 | var deferred = {}; |
michael@0 | 1101 | var promise = new Promise(function (resolve, reject) { |
michael@0 | 1102 | deferred.resolve = resolve; |
michael@0 | 1103 | deferred.reject = reject; |
michael@0 | 1104 | }); |
michael@0 | 1105 | deferred.promise = promise; |
michael@0 | 1106 | promise.then(function(resolvedData) { |
michael@0 | 1107 | comObj.postMessage({ |
michael@0 | 1108 | isReply: true, |
michael@0 | 1109 | callbackId: data.callbackId, |
michael@0 | 1110 | data: resolvedData |
michael@0 | 1111 | }); |
michael@0 | 1112 | }); |
michael@0 | 1113 | action[0].call(action[1], data.data, deferred); |
michael@0 | 1114 | } else { |
michael@0 | 1115 | action[0].call(action[1], data.data); |
michael@0 | 1116 | } |
michael@0 | 1117 | } else { |
michael@0 | 1118 | error('Unkown action from worker: ' + data.action); |
michael@0 | 1119 | } |
michael@0 | 1120 | }; |
michael@0 | 1121 | } |
michael@0 | 1122 | |
michael@0 | 1123 | MessageHandler.prototype = { |
michael@0 | 1124 | on: function messageHandlerOn(actionName, handler, scope) { |
michael@0 | 1125 | var ah = this.actionHandler; |
michael@0 | 1126 | if (ah[actionName]) { |
michael@0 | 1127 | error('There is already an actionName called "' + actionName + '"'); |
michael@0 | 1128 | } |
michael@0 | 1129 | ah[actionName] = [handler, scope]; |
michael@0 | 1130 | }, |
michael@0 | 1131 | /** |
michael@0 | 1132 | * Sends a message to the comObj to invoke the action with the supplied data. |
michael@0 | 1133 | * @param {String} actionName Action to call. |
michael@0 | 1134 | * @param {JSON} data JSON data to send. |
michael@0 | 1135 | * @param {function} [callback] Optional callback that will handle a reply. |
michael@0 | 1136 | * @param {Array} [transfers] Optional list of transfers/ArrayBuffers |
michael@0 | 1137 | */ |
michael@0 | 1138 | send: function messageHandlerSend(actionName, data, callback, transfers) { |
michael@0 | 1139 | var message = { |
michael@0 | 1140 | action: actionName, |
michael@0 | 1141 | data: data |
michael@0 | 1142 | }; |
michael@0 | 1143 | if (callback) { |
michael@0 | 1144 | var callbackId = this.callbackIndex++; |
michael@0 | 1145 | this.callbacks[callbackId] = callback; |
michael@0 | 1146 | message.callbackId = callbackId; |
michael@0 | 1147 | } |
michael@0 | 1148 | if (transfers && this.postMessageTransfers) { |
michael@0 | 1149 | this.comObj.postMessage(message, transfers); |
michael@0 | 1150 | } else { |
michael@0 | 1151 | this.comObj.postMessage(message); |
michael@0 | 1152 | } |
michael@0 | 1153 | } |
michael@0 | 1154 | }; |
michael@0 | 1155 | |
michael@0 | 1156 | function loadJpegStream(id, imageUrl, objs) { |
michael@0 | 1157 | var img = new Image(); |
michael@0 | 1158 | img.onload = (function loadJpegStream_onloadClosure() { |
michael@0 | 1159 | objs.resolve(id, img); |
michael@0 | 1160 | }); |
michael@0 | 1161 | img.src = imageUrl; |
michael@0 | 1162 | } |
michael@0 | 1163 | |
michael@0 | 1164 | |
michael@0 | 1165 | var ColorSpace = (function ColorSpaceClosure() { |
michael@0 | 1166 | // Constructor should define this.numComps, this.defaultColor, this.name |
michael@0 | 1167 | function ColorSpace() { |
michael@0 | 1168 | error('should not call ColorSpace constructor'); |
michael@0 | 1169 | } |
michael@0 | 1170 | |
michael@0 | 1171 | ColorSpace.prototype = { |
michael@0 | 1172 | /** |
michael@0 | 1173 | * Converts the color value to the RGB color. The color components are |
michael@0 | 1174 | * located in the src array starting from the srcOffset. Returns the array |
michael@0 | 1175 | * of the rgb components, each value ranging from [0,255]. |
michael@0 | 1176 | */ |
michael@0 | 1177 | getRgb: function ColorSpace_getRgb(src, srcOffset) { |
michael@0 | 1178 | var rgb = new Uint8Array(3); |
michael@0 | 1179 | this.getRgbItem(src, srcOffset, rgb, 0); |
michael@0 | 1180 | return rgb; |
michael@0 | 1181 | }, |
michael@0 | 1182 | /** |
michael@0 | 1183 | * Converts the color value to the RGB color, similar to the getRgb method. |
michael@0 | 1184 | * The result placed into the dest array starting from the destOffset. |
michael@0 | 1185 | */ |
michael@0 | 1186 | getRgbItem: function ColorSpace_getRgbItem(src, srcOffset, |
michael@0 | 1187 | dest, destOffset) { |
michael@0 | 1188 | error('Should not call ColorSpace.getRgbItem'); |
michael@0 | 1189 | }, |
michael@0 | 1190 | /** |
michael@0 | 1191 | * Converts the specified number of the color values to the RGB colors. |
michael@0 | 1192 | * The colors are located in the src array starting from the srcOffset. |
michael@0 | 1193 | * The result is placed into the dest array starting from the destOffset. |
michael@0 | 1194 | * The src array items shall be in [0,2^bits) range, the dest array items |
michael@0 | 1195 | * will be in [0,255] range. alpha01 indicates how many alpha components |
michael@0 | 1196 | * there are in the dest array; it will be either 0 (RGB array) or 1 (RGBA |
michael@0 | 1197 | * array). |
michael@0 | 1198 | */ |
michael@0 | 1199 | getRgbBuffer: function ColorSpace_getRgbBuffer(src, srcOffset, count, |
michael@0 | 1200 | dest, destOffset, bits, |
michael@0 | 1201 | alpha01) { |
michael@0 | 1202 | error('Should not call ColorSpace.getRgbBuffer'); |
michael@0 | 1203 | }, |
michael@0 | 1204 | /** |
michael@0 | 1205 | * Determines the number of bytes required to store the result of the |
michael@0 | 1206 | * conversion done by the getRgbBuffer method. As in getRgbBuffer, |
michael@0 | 1207 | * |alpha01| is either 0 (RGB output) or 1 (RGBA output). |
michael@0 | 1208 | */ |
michael@0 | 1209 | getOutputLength: function ColorSpace_getOutputLength(inputLength, |
michael@0 | 1210 | alpha01) { |
michael@0 | 1211 | error('Should not call ColorSpace.getOutputLength'); |
michael@0 | 1212 | }, |
michael@0 | 1213 | /** |
michael@0 | 1214 | * Returns true if source data will be equal the result/output data. |
michael@0 | 1215 | */ |
michael@0 | 1216 | isPassthrough: function ColorSpace_isPassthrough(bits) { |
michael@0 | 1217 | return false; |
michael@0 | 1218 | }, |
michael@0 | 1219 | /** |
michael@0 | 1220 | * Fills in the RGB colors in the destination buffer. alpha01 indicates |
michael@0 | 1221 | * how many alpha components there are in the dest array; it will be either |
michael@0 | 1222 | * 0 (RGB array) or 1 (RGBA array). |
michael@0 | 1223 | */ |
michael@0 | 1224 | fillRgb: function ColorSpace_fillRgb(dest, originalWidth, |
michael@0 | 1225 | originalHeight, width, height, |
michael@0 | 1226 | actualHeight, bpc, comps, alpha01) { |
michael@0 | 1227 | var count = originalWidth * originalHeight; |
michael@0 | 1228 | var rgbBuf = null; |
michael@0 | 1229 | var numComponentColors = 1 << bpc; |
michael@0 | 1230 | var needsResizing = originalHeight != height || originalWidth != width; |
michael@0 | 1231 | var i, ii; |
michael@0 | 1232 | |
michael@0 | 1233 | if (this.isPassthrough(bpc)) { |
michael@0 | 1234 | rgbBuf = comps; |
michael@0 | 1235 | } else if (this.numComps === 1 && count > numComponentColors && |
michael@0 | 1236 | this.name !== 'DeviceGray' && this.name !== 'DeviceRGB') { |
michael@0 | 1237 | // Optimization: create a color map when there is just one component and |
michael@0 | 1238 | // we are converting more colors than the size of the color map. We |
michael@0 | 1239 | // don't build the map if the colorspace is gray or rgb since those |
michael@0 | 1240 | // methods are faster than building a map. This mainly offers big speed |
michael@0 | 1241 | // ups for indexed and alternate colorspaces. |
michael@0 | 1242 | // |
michael@0 | 1243 | // TODO it may be worth while to cache the color map. While running |
michael@0 | 1244 | // testing I never hit a cache so I will leave that out for now (perhaps |
michael@0 | 1245 | // we are reparsing colorspaces too much?). |
michael@0 | 1246 | var allColors = bpc <= 8 ? new Uint8Array(numComponentColors) : |
michael@0 | 1247 | new Uint16Array(numComponentColors); |
michael@0 | 1248 | var key; |
michael@0 | 1249 | for (i = 0; i < numComponentColors; i++) { |
michael@0 | 1250 | allColors[i] = i; |
michael@0 | 1251 | } |
michael@0 | 1252 | var colorMap = new Uint8Array(numComponentColors * 3); |
michael@0 | 1253 | this.getRgbBuffer(allColors, 0, numComponentColors, colorMap, 0, bpc, |
michael@0 | 1254 | /* alpha01 = */ 0); |
michael@0 | 1255 | |
michael@0 | 1256 | var destPos, rgbPos; |
michael@0 | 1257 | if (!needsResizing) { |
michael@0 | 1258 | // Fill in the RGB values directly into |dest|. |
michael@0 | 1259 | destPos = 0; |
michael@0 | 1260 | for (i = 0; i < count; ++i) { |
michael@0 | 1261 | key = comps[i] * 3; |
michael@0 | 1262 | dest[destPos++] = colorMap[key]; |
michael@0 | 1263 | dest[destPos++] = colorMap[key + 1]; |
michael@0 | 1264 | dest[destPos++] = colorMap[key + 2]; |
michael@0 | 1265 | destPos += alpha01; |
michael@0 | 1266 | } |
michael@0 | 1267 | } else { |
michael@0 | 1268 | rgbBuf = new Uint8Array(count * 3); |
michael@0 | 1269 | rgbPos = 0; |
michael@0 | 1270 | for (i = 0; i < count; ++i) { |
michael@0 | 1271 | key = comps[i] * 3; |
michael@0 | 1272 | rgbBuf[rgbPos++] = colorMap[key]; |
michael@0 | 1273 | rgbBuf[rgbPos++] = colorMap[key + 1]; |
michael@0 | 1274 | rgbBuf[rgbPos++] = colorMap[key + 2]; |
michael@0 | 1275 | } |
michael@0 | 1276 | } |
michael@0 | 1277 | } else { |
michael@0 | 1278 | if (!needsResizing) { |
michael@0 | 1279 | // Fill in the RGB values directly into |dest|. |
michael@0 | 1280 | this.getRgbBuffer(comps, 0, width * actualHeight, dest, 0, bpc, |
michael@0 | 1281 | alpha01); |
michael@0 | 1282 | } else { |
michael@0 | 1283 | rgbBuf = new Uint8Array(count * 3); |
michael@0 | 1284 | this.getRgbBuffer(comps, 0, count, rgbBuf, 0, bpc, |
michael@0 | 1285 | /* alpha01 = */ 0); |
michael@0 | 1286 | } |
michael@0 | 1287 | } |
michael@0 | 1288 | |
michael@0 | 1289 | if (rgbBuf) { |
michael@0 | 1290 | if (needsResizing) { |
michael@0 | 1291 | rgbBuf = PDFImage.resize(rgbBuf, bpc, 3, originalWidth, |
michael@0 | 1292 | originalHeight, width, height); |
michael@0 | 1293 | } |
michael@0 | 1294 | rgbPos = 0; |
michael@0 | 1295 | destPos = 0; |
michael@0 | 1296 | for (i = 0, ii = width * actualHeight; i < ii; i++) { |
michael@0 | 1297 | dest[destPos++] = rgbBuf[rgbPos++]; |
michael@0 | 1298 | dest[destPos++] = rgbBuf[rgbPos++]; |
michael@0 | 1299 | dest[destPos++] = rgbBuf[rgbPos++]; |
michael@0 | 1300 | destPos += alpha01; |
michael@0 | 1301 | } |
michael@0 | 1302 | } |
michael@0 | 1303 | }, |
michael@0 | 1304 | /** |
michael@0 | 1305 | * True if the colorspace has components in the default range of [0, 1]. |
michael@0 | 1306 | * This should be true for all colorspaces except for lab color spaces |
michael@0 | 1307 | * which are [0,100], [-128, 127], [-128, 127]. |
michael@0 | 1308 | */ |
michael@0 | 1309 | usesZeroToOneRange: true |
michael@0 | 1310 | }; |
michael@0 | 1311 | |
michael@0 | 1312 | ColorSpace.parse = function ColorSpace_parse(cs, xref, res) { |
michael@0 | 1313 | var IR = ColorSpace.parseToIR(cs, xref, res); |
michael@0 | 1314 | if (IR instanceof AlternateCS) { |
michael@0 | 1315 | return IR; |
michael@0 | 1316 | } |
michael@0 | 1317 | return ColorSpace.fromIR(IR); |
michael@0 | 1318 | }; |
michael@0 | 1319 | |
michael@0 | 1320 | ColorSpace.fromIR = function ColorSpace_fromIR(IR) { |
michael@0 | 1321 | var name = isArray(IR) ? IR[0] : IR; |
michael@0 | 1322 | var whitePoint, blackPoint; |
michael@0 | 1323 | |
michael@0 | 1324 | switch (name) { |
michael@0 | 1325 | case 'DeviceGrayCS': |
michael@0 | 1326 | return this.singletons.gray; |
michael@0 | 1327 | case 'DeviceRgbCS': |
michael@0 | 1328 | return this.singletons.rgb; |
michael@0 | 1329 | case 'DeviceCmykCS': |
michael@0 | 1330 | return this.singletons.cmyk; |
michael@0 | 1331 | case 'CalGrayCS': |
michael@0 | 1332 | whitePoint = IR[1].WhitePoint; |
michael@0 | 1333 | blackPoint = IR[1].BlackPoint; |
michael@0 | 1334 | var gamma = IR[1].Gamma; |
michael@0 | 1335 | return new CalGrayCS(whitePoint, blackPoint, gamma); |
michael@0 | 1336 | case 'PatternCS': |
michael@0 | 1337 | var basePatternCS = IR[1]; |
michael@0 | 1338 | if (basePatternCS) { |
michael@0 | 1339 | basePatternCS = ColorSpace.fromIR(basePatternCS); |
michael@0 | 1340 | } |
michael@0 | 1341 | return new PatternCS(basePatternCS); |
michael@0 | 1342 | case 'IndexedCS': |
michael@0 | 1343 | var baseIndexedCS = IR[1]; |
michael@0 | 1344 | var hiVal = IR[2]; |
michael@0 | 1345 | var lookup = IR[3]; |
michael@0 | 1346 | return new IndexedCS(ColorSpace.fromIR(baseIndexedCS), hiVal, lookup); |
michael@0 | 1347 | case 'AlternateCS': |
michael@0 | 1348 | var numComps = IR[1]; |
michael@0 | 1349 | var alt = IR[2]; |
michael@0 | 1350 | var tintFnIR = IR[3]; |
michael@0 | 1351 | |
michael@0 | 1352 | return new AlternateCS(numComps, ColorSpace.fromIR(alt), |
michael@0 | 1353 | PDFFunction.fromIR(tintFnIR)); |
michael@0 | 1354 | case 'LabCS': |
michael@0 | 1355 | whitePoint = IR[1].WhitePoint; |
michael@0 | 1356 | blackPoint = IR[1].BlackPoint; |
michael@0 | 1357 | var range = IR[1].Range; |
michael@0 | 1358 | return new LabCS(whitePoint, blackPoint, range); |
michael@0 | 1359 | default: |
michael@0 | 1360 | error('Unkown name ' + name); |
michael@0 | 1361 | } |
michael@0 | 1362 | return null; |
michael@0 | 1363 | }; |
michael@0 | 1364 | |
michael@0 | 1365 | ColorSpace.parseToIR = function ColorSpace_parseToIR(cs, xref, res) { |
michael@0 | 1366 | if (isName(cs)) { |
michael@0 | 1367 | var colorSpaces = res.get('ColorSpace'); |
michael@0 | 1368 | if (isDict(colorSpaces)) { |
michael@0 | 1369 | var refcs = colorSpaces.get(cs.name); |
michael@0 | 1370 | if (refcs) { |
michael@0 | 1371 | cs = refcs; |
michael@0 | 1372 | } |
michael@0 | 1373 | } |
michael@0 | 1374 | } |
michael@0 | 1375 | |
michael@0 | 1376 | cs = xref.fetchIfRef(cs); |
michael@0 | 1377 | var mode; |
michael@0 | 1378 | |
michael@0 | 1379 | if (isName(cs)) { |
michael@0 | 1380 | mode = cs.name; |
michael@0 | 1381 | this.mode = mode; |
michael@0 | 1382 | |
michael@0 | 1383 | switch (mode) { |
michael@0 | 1384 | case 'DeviceGray': |
michael@0 | 1385 | case 'G': |
michael@0 | 1386 | return 'DeviceGrayCS'; |
michael@0 | 1387 | case 'DeviceRGB': |
michael@0 | 1388 | case 'RGB': |
michael@0 | 1389 | return 'DeviceRgbCS'; |
michael@0 | 1390 | case 'DeviceCMYK': |
michael@0 | 1391 | case 'CMYK': |
michael@0 | 1392 | return 'DeviceCmykCS'; |
michael@0 | 1393 | case 'Pattern': |
michael@0 | 1394 | return ['PatternCS', null]; |
michael@0 | 1395 | default: |
michael@0 | 1396 | error('unrecognized colorspace ' + mode); |
michael@0 | 1397 | } |
michael@0 | 1398 | } else if (isArray(cs)) { |
michael@0 | 1399 | mode = cs[0].name; |
michael@0 | 1400 | this.mode = mode; |
michael@0 | 1401 | var numComps, params; |
michael@0 | 1402 | |
michael@0 | 1403 | switch (mode) { |
michael@0 | 1404 | case 'DeviceGray': |
michael@0 | 1405 | case 'G': |
michael@0 | 1406 | return 'DeviceGrayCS'; |
michael@0 | 1407 | case 'DeviceRGB': |
michael@0 | 1408 | case 'RGB': |
michael@0 | 1409 | return 'DeviceRgbCS'; |
michael@0 | 1410 | case 'DeviceCMYK': |
michael@0 | 1411 | case 'CMYK': |
michael@0 | 1412 | return 'DeviceCmykCS'; |
michael@0 | 1413 | case 'CalGray': |
michael@0 | 1414 | params = cs[1].getAll(); |
michael@0 | 1415 | return ['CalGrayCS', params]; |
michael@0 | 1416 | case 'CalRGB': |
michael@0 | 1417 | return 'DeviceRgbCS'; |
michael@0 | 1418 | case 'ICCBased': |
michael@0 | 1419 | var stream = xref.fetchIfRef(cs[1]); |
michael@0 | 1420 | var dict = stream.dict; |
michael@0 | 1421 | numComps = dict.get('N'); |
michael@0 | 1422 | if (numComps == 1) { |
michael@0 | 1423 | return 'DeviceGrayCS'; |
michael@0 | 1424 | } else if (numComps == 3) { |
michael@0 | 1425 | return 'DeviceRgbCS'; |
michael@0 | 1426 | } else if (numComps == 4) { |
michael@0 | 1427 | return 'DeviceCmykCS'; |
michael@0 | 1428 | } |
michael@0 | 1429 | break; |
michael@0 | 1430 | case 'Pattern': |
michael@0 | 1431 | var basePatternCS = cs[1]; |
michael@0 | 1432 | if (basePatternCS) { |
michael@0 | 1433 | basePatternCS = ColorSpace.parseToIR(basePatternCS, xref, res); |
michael@0 | 1434 | } |
michael@0 | 1435 | return ['PatternCS', basePatternCS]; |
michael@0 | 1436 | case 'Indexed': |
michael@0 | 1437 | case 'I': |
michael@0 | 1438 | var baseIndexedCS = ColorSpace.parseToIR(cs[1], xref, res); |
michael@0 | 1439 | var hiVal = cs[2] + 1; |
michael@0 | 1440 | var lookup = xref.fetchIfRef(cs[3]); |
michael@0 | 1441 | if (isStream(lookup)) { |
michael@0 | 1442 | lookup = lookup.getBytes(); |
michael@0 | 1443 | } |
michael@0 | 1444 | return ['IndexedCS', baseIndexedCS, hiVal, lookup]; |
michael@0 | 1445 | case 'Separation': |
michael@0 | 1446 | case 'DeviceN': |
michael@0 | 1447 | var name = cs[1]; |
michael@0 | 1448 | numComps = 1; |
michael@0 | 1449 | if (isName(name)) { |
michael@0 | 1450 | numComps = 1; |
michael@0 | 1451 | } else if (isArray(name)) { |
michael@0 | 1452 | numComps = name.length; |
michael@0 | 1453 | } |
michael@0 | 1454 | var alt = ColorSpace.parseToIR(cs[2], xref, res); |
michael@0 | 1455 | var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3])); |
michael@0 | 1456 | return ['AlternateCS', numComps, alt, tintFnIR]; |
michael@0 | 1457 | case 'Lab': |
michael@0 | 1458 | params = cs[1].getAll(); |
michael@0 | 1459 | return ['LabCS', params]; |
michael@0 | 1460 | default: |
michael@0 | 1461 | error('unimplemented color space object "' + mode + '"'); |
michael@0 | 1462 | } |
michael@0 | 1463 | } else { |
michael@0 | 1464 | error('unrecognized color space object: "' + cs + '"'); |
michael@0 | 1465 | } |
michael@0 | 1466 | return null; |
michael@0 | 1467 | }; |
michael@0 | 1468 | /** |
michael@0 | 1469 | * Checks if a decode map matches the default decode map for a color space. |
michael@0 | 1470 | * This handles the general decode maps where there are two values per |
michael@0 | 1471 | * component. e.g. [0, 1, 0, 1, 0, 1] for a RGB color. |
michael@0 | 1472 | * This does not handle Lab, Indexed, or Pattern decode maps since they are |
michael@0 | 1473 | * slightly different. |
michael@0 | 1474 | * @param {Array} decode Decode map (usually from an image). |
michael@0 | 1475 | * @param {Number} n Number of components the color space has. |
michael@0 | 1476 | */ |
michael@0 | 1477 | ColorSpace.isDefaultDecode = function ColorSpace_isDefaultDecode(decode, n) { |
michael@0 | 1478 | if (!decode) { |
michael@0 | 1479 | return true; |
michael@0 | 1480 | } |
michael@0 | 1481 | |
michael@0 | 1482 | if (n * 2 !== decode.length) { |
michael@0 | 1483 | warn('The decode map is not the correct length'); |
michael@0 | 1484 | return true; |
michael@0 | 1485 | } |
michael@0 | 1486 | for (var i = 0, ii = decode.length; i < ii; i += 2) { |
michael@0 | 1487 | if (decode[i] !== 0 || decode[i + 1] != 1) { |
michael@0 | 1488 | return false; |
michael@0 | 1489 | } |
michael@0 | 1490 | } |
michael@0 | 1491 | return true; |
michael@0 | 1492 | }; |
michael@0 | 1493 | |
michael@0 | 1494 | ColorSpace.singletons = { |
michael@0 | 1495 | get gray() { |
michael@0 | 1496 | return shadow(this, 'gray', new DeviceGrayCS()); |
michael@0 | 1497 | }, |
michael@0 | 1498 | get rgb() { |
michael@0 | 1499 | return shadow(this, 'rgb', new DeviceRgbCS()); |
michael@0 | 1500 | }, |
michael@0 | 1501 | get cmyk() { |
michael@0 | 1502 | return shadow(this, 'cmyk', new DeviceCmykCS()); |
michael@0 | 1503 | } |
michael@0 | 1504 | }; |
michael@0 | 1505 | |
michael@0 | 1506 | return ColorSpace; |
michael@0 | 1507 | })(); |
michael@0 | 1508 | |
michael@0 | 1509 | /** |
michael@0 | 1510 | * Alternate color space handles both Separation and DeviceN color spaces. A |
michael@0 | 1511 | * Separation color space is actually just a DeviceN with one color component. |
michael@0 | 1512 | * Both color spaces use a tinting function to convert colors to a base color |
michael@0 | 1513 | * space. |
michael@0 | 1514 | */ |
michael@0 | 1515 | var AlternateCS = (function AlternateCSClosure() { |
michael@0 | 1516 | function AlternateCS(numComps, base, tintFn) { |
michael@0 | 1517 | this.name = 'Alternate'; |
michael@0 | 1518 | this.numComps = numComps; |
michael@0 | 1519 | this.defaultColor = new Float32Array(numComps); |
michael@0 | 1520 | for (var i = 0; i < numComps; ++i) { |
michael@0 | 1521 | this.defaultColor[i] = 1; |
michael@0 | 1522 | } |
michael@0 | 1523 | this.base = base; |
michael@0 | 1524 | this.tintFn = tintFn; |
michael@0 | 1525 | } |
michael@0 | 1526 | |
michael@0 | 1527 | AlternateCS.prototype = { |
michael@0 | 1528 | getRgb: ColorSpace.prototype.getRgb, |
michael@0 | 1529 | getRgbItem: function AlternateCS_getRgbItem(src, srcOffset, |
michael@0 | 1530 | dest, destOffset) { |
michael@0 | 1531 | var baseNumComps = this.base.numComps; |
michael@0 | 1532 | var input = 'subarray' in src ? |
michael@0 | 1533 | src.subarray(srcOffset, srcOffset + this.numComps) : |
michael@0 | 1534 | Array.prototype.slice.call(src, srcOffset, srcOffset + this.numComps); |
michael@0 | 1535 | var tinted = this.tintFn(input); |
michael@0 | 1536 | this.base.getRgbItem(tinted, 0, dest, destOffset); |
michael@0 | 1537 | }, |
michael@0 | 1538 | getRgbBuffer: function AlternateCS_getRgbBuffer(src, srcOffset, count, |
michael@0 | 1539 | dest, destOffset, bits, |
michael@0 | 1540 | alpha01) { |
michael@0 | 1541 | var tintFn = this.tintFn; |
michael@0 | 1542 | var base = this.base; |
michael@0 | 1543 | var scale = 1 / ((1 << bits) - 1); |
michael@0 | 1544 | var baseNumComps = base.numComps; |
michael@0 | 1545 | var usesZeroToOneRange = base.usesZeroToOneRange; |
michael@0 | 1546 | var isPassthrough = (base.isPassthrough(8) || !usesZeroToOneRange) && |
michael@0 | 1547 | alpha01 === 0; |
michael@0 | 1548 | var pos = isPassthrough ? destOffset : 0; |
michael@0 | 1549 | var baseBuf = isPassthrough ? dest : new Uint8Array(baseNumComps * count); |
michael@0 | 1550 | var numComps = this.numComps; |
michael@0 | 1551 | |
michael@0 | 1552 | var scaled = new Float32Array(numComps); |
michael@0 | 1553 | var i, j; |
michael@0 | 1554 | for (i = 0; i < count; i++) { |
michael@0 | 1555 | for (j = 0; j < numComps; j++) { |
michael@0 | 1556 | scaled[j] = src[srcOffset++] * scale; |
michael@0 | 1557 | } |
michael@0 | 1558 | var tinted = tintFn(scaled); |
michael@0 | 1559 | if (usesZeroToOneRange) { |
michael@0 | 1560 | for (j = 0; j < baseNumComps; j++) { |
michael@0 | 1561 | baseBuf[pos++] = tinted[j] * 255; |
michael@0 | 1562 | } |
michael@0 | 1563 | } else { |
michael@0 | 1564 | base.getRgbItem(tinted, 0, baseBuf, pos); |
michael@0 | 1565 | pos += baseNumComps; |
michael@0 | 1566 | } |
michael@0 | 1567 | } |
michael@0 | 1568 | if (!isPassthrough) { |
michael@0 | 1569 | base.getRgbBuffer(baseBuf, 0, count, dest, destOffset, 8, alpha01); |
michael@0 | 1570 | } |
michael@0 | 1571 | }, |
michael@0 | 1572 | getOutputLength: function AlternateCS_getOutputLength(inputLength, |
michael@0 | 1573 | alpha01) { |
michael@0 | 1574 | return this.base.getOutputLength(inputLength * |
michael@0 | 1575 | this.base.numComps / this.numComps, |
michael@0 | 1576 | alpha01); |
michael@0 | 1577 | }, |
michael@0 | 1578 | isPassthrough: ColorSpace.prototype.isPassthrough, |
michael@0 | 1579 | fillRgb: ColorSpace.prototype.fillRgb, |
michael@0 | 1580 | isDefaultDecode: function AlternateCS_isDefaultDecode(decodeMap) { |
michael@0 | 1581 | return ColorSpace.isDefaultDecode(decodeMap, this.numComps); |
michael@0 | 1582 | }, |
michael@0 | 1583 | usesZeroToOneRange: true |
michael@0 | 1584 | }; |
michael@0 | 1585 | |
michael@0 | 1586 | return AlternateCS; |
michael@0 | 1587 | })(); |
michael@0 | 1588 | |
michael@0 | 1589 | var PatternCS = (function PatternCSClosure() { |
michael@0 | 1590 | function PatternCS(baseCS) { |
michael@0 | 1591 | this.name = 'Pattern'; |
michael@0 | 1592 | this.base = baseCS; |
michael@0 | 1593 | } |
michael@0 | 1594 | PatternCS.prototype = {}; |
michael@0 | 1595 | |
michael@0 | 1596 | return PatternCS; |
michael@0 | 1597 | })(); |
michael@0 | 1598 | |
michael@0 | 1599 | var IndexedCS = (function IndexedCSClosure() { |
michael@0 | 1600 | function IndexedCS(base, highVal, lookup) { |
michael@0 | 1601 | this.name = 'Indexed'; |
michael@0 | 1602 | this.numComps = 1; |
michael@0 | 1603 | this.defaultColor = new Uint8Array([0]); |
michael@0 | 1604 | this.base = base; |
michael@0 | 1605 | this.highVal = highVal; |
michael@0 | 1606 | |
michael@0 | 1607 | var baseNumComps = base.numComps; |
michael@0 | 1608 | var length = baseNumComps * highVal; |
michael@0 | 1609 | var lookupArray; |
michael@0 | 1610 | |
michael@0 | 1611 | if (isStream(lookup)) { |
michael@0 | 1612 | lookupArray = new Uint8Array(length); |
michael@0 | 1613 | var bytes = lookup.getBytes(length); |
michael@0 | 1614 | lookupArray.set(bytes); |
michael@0 | 1615 | } else if (isString(lookup)) { |
michael@0 | 1616 | lookupArray = new Uint8Array(length); |
michael@0 | 1617 | for (var i = 0; i < length; ++i) { |
michael@0 | 1618 | lookupArray[i] = lookup.charCodeAt(i); |
michael@0 | 1619 | } |
michael@0 | 1620 | } else if (lookup instanceof Uint8Array || lookup instanceof Array) { |
michael@0 | 1621 | lookupArray = lookup; |
michael@0 | 1622 | } else { |
michael@0 | 1623 | error('Unrecognized lookup table: ' + lookup); |
michael@0 | 1624 | } |
michael@0 | 1625 | this.lookup = lookupArray; |
michael@0 | 1626 | } |
michael@0 | 1627 | |
michael@0 | 1628 | IndexedCS.prototype = { |
michael@0 | 1629 | getRgb: ColorSpace.prototype.getRgb, |
michael@0 | 1630 | getRgbItem: function IndexedCS_getRgbItem(src, srcOffset, |
michael@0 | 1631 | dest, destOffset) { |
michael@0 | 1632 | var numComps = this.base.numComps; |
michael@0 | 1633 | var start = src[srcOffset] * numComps; |
michael@0 | 1634 | this.base.getRgbItem(this.lookup, start, dest, destOffset); |
michael@0 | 1635 | }, |
michael@0 | 1636 | getRgbBuffer: function IndexedCS_getRgbBuffer(src, srcOffset, count, |
michael@0 | 1637 | dest, destOffset, bits, |
michael@0 | 1638 | alpha01) { |
michael@0 | 1639 | var base = this.base; |
michael@0 | 1640 | var numComps = base.numComps; |
michael@0 | 1641 | var outputDelta = base.getOutputLength(numComps, alpha01); |
michael@0 | 1642 | var lookup = this.lookup; |
michael@0 | 1643 | |
michael@0 | 1644 | for (var i = 0; i < count; ++i) { |
michael@0 | 1645 | var lookupPos = src[srcOffset++] * numComps; |
michael@0 | 1646 | base.getRgbBuffer(lookup, lookupPos, 1, dest, destOffset, 8, alpha01); |
michael@0 | 1647 | destOffset += outputDelta; |
michael@0 | 1648 | } |
michael@0 | 1649 | }, |
michael@0 | 1650 | getOutputLength: function IndexedCS_getOutputLength(inputLength, alpha01) { |
michael@0 | 1651 | return this.base.getOutputLength(inputLength * this.base.numComps, |
michael@0 | 1652 | alpha01); |
michael@0 | 1653 | }, |
michael@0 | 1654 | isPassthrough: ColorSpace.prototype.isPassthrough, |
michael@0 | 1655 | fillRgb: ColorSpace.prototype.fillRgb, |
michael@0 | 1656 | isDefaultDecode: function IndexedCS_isDefaultDecode(decodeMap) { |
michael@0 | 1657 | // indexed color maps shouldn't be changed |
michael@0 | 1658 | return true; |
michael@0 | 1659 | }, |
michael@0 | 1660 | usesZeroToOneRange: true |
michael@0 | 1661 | }; |
michael@0 | 1662 | return IndexedCS; |
michael@0 | 1663 | })(); |
michael@0 | 1664 | |
michael@0 | 1665 | var DeviceGrayCS = (function DeviceGrayCSClosure() { |
michael@0 | 1666 | function DeviceGrayCS() { |
michael@0 | 1667 | this.name = 'DeviceGray'; |
michael@0 | 1668 | this.numComps = 1; |
michael@0 | 1669 | this.defaultColor = new Float32Array([0]); |
michael@0 | 1670 | } |
michael@0 | 1671 | |
michael@0 | 1672 | DeviceGrayCS.prototype = { |
michael@0 | 1673 | getRgb: ColorSpace.prototype.getRgb, |
michael@0 | 1674 | getRgbItem: function DeviceGrayCS_getRgbItem(src, srcOffset, |
michael@0 | 1675 | dest, destOffset) { |
michael@0 | 1676 | var c = (src[srcOffset] * 255) | 0; |
michael@0 | 1677 | c = c < 0 ? 0 : c > 255 ? 255 : c; |
michael@0 | 1678 | dest[destOffset] = dest[destOffset + 1] = dest[destOffset + 2] = c; |
michael@0 | 1679 | }, |
michael@0 | 1680 | getRgbBuffer: function DeviceGrayCS_getRgbBuffer(src, srcOffset, count, |
michael@0 | 1681 | dest, destOffset, bits, |
michael@0 | 1682 | alpha01) { |
michael@0 | 1683 | var scale = 255 / ((1 << bits) - 1); |
michael@0 | 1684 | var j = srcOffset, q = destOffset; |
michael@0 | 1685 | for (var i = 0; i < count; ++i) { |
michael@0 | 1686 | var c = (scale * src[j++]) | 0; |
michael@0 | 1687 | dest[q++] = c; |
michael@0 | 1688 | dest[q++] = c; |
michael@0 | 1689 | dest[q++] = c; |
michael@0 | 1690 | q += alpha01; |
michael@0 | 1691 | } |
michael@0 | 1692 | }, |
michael@0 | 1693 | getOutputLength: function DeviceGrayCS_getOutputLength(inputLength, |
michael@0 | 1694 | alpha01) { |
michael@0 | 1695 | return inputLength * (3 + alpha01); |
michael@0 | 1696 | }, |
michael@0 | 1697 | isPassthrough: ColorSpace.prototype.isPassthrough, |
michael@0 | 1698 | fillRgb: ColorSpace.prototype.fillRgb, |
michael@0 | 1699 | isDefaultDecode: function DeviceGrayCS_isDefaultDecode(decodeMap) { |
michael@0 | 1700 | return ColorSpace.isDefaultDecode(decodeMap, this.numComps); |
michael@0 | 1701 | }, |
michael@0 | 1702 | usesZeroToOneRange: true |
michael@0 | 1703 | }; |
michael@0 | 1704 | return DeviceGrayCS; |
michael@0 | 1705 | })(); |
michael@0 | 1706 | |
michael@0 | 1707 | var DeviceRgbCS = (function DeviceRgbCSClosure() { |
michael@0 | 1708 | function DeviceRgbCS() { |
michael@0 | 1709 | this.name = 'DeviceRGB'; |
michael@0 | 1710 | this.numComps = 3; |
michael@0 | 1711 | this.defaultColor = new Float32Array([0, 0, 0]); |
michael@0 | 1712 | } |
michael@0 | 1713 | DeviceRgbCS.prototype = { |
michael@0 | 1714 | getRgb: ColorSpace.prototype.getRgb, |
michael@0 | 1715 | getRgbItem: function DeviceRgbCS_getRgbItem(src, srcOffset, |
michael@0 | 1716 | dest, destOffset) { |
michael@0 | 1717 | var r = (src[srcOffset] * 255) | 0; |
michael@0 | 1718 | var g = (src[srcOffset + 1] * 255) | 0; |
michael@0 | 1719 | var b = (src[srcOffset + 2] * 255) | 0; |
michael@0 | 1720 | dest[destOffset] = r < 0 ? 0 : r > 255 ? 255 : r; |
michael@0 | 1721 | dest[destOffset + 1] = g < 0 ? 0 : g > 255 ? 255 : g; |
michael@0 | 1722 | dest[destOffset + 2] = b < 0 ? 0 : b > 255 ? 255 : b; |
michael@0 | 1723 | }, |
michael@0 | 1724 | getRgbBuffer: function DeviceRgbCS_getRgbBuffer(src, srcOffset, count, |
michael@0 | 1725 | dest, destOffset, bits, |
michael@0 | 1726 | alpha01) { |
michael@0 | 1727 | if (bits === 8 && alpha01 === 0) { |
michael@0 | 1728 | dest.set(src.subarray(srcOffset, srcOffset + count * 3), destOffset); |
michael@0 | 1729 | return; |
michael@0 | 1730 | } |
michael@0 | 1731 | var scale = 255 / ((1 << bits) - 1); |
michael@0 | 1732 | var j = srcOffset, q = destOffset; |
michael@0 | 1733 | for (var i = 0; i < count; ++i) { |
michael@0 | 1734 | dest[q++] = (scale * src[j++]) | 0; |
michael@0 | 1735 | dest[q++] = (scale * src[j++]) | 0; |
michael@0 | 1736 | dest[q++] = (scale * src[j++]) | 0; |
michael@0 | 1737 | q += alpha01; |
michael@0 | 1738 | } |
michael@0 | 1739 | }, |
michael@0 | 1740 | getOutputLength: function DeviceRgbCS_getOutputLength(inputLength, |
michael@0 | 1741 | alpha01) { |
michael@0 | 1742 | return (inputLength * (3 + alpha01) / 3) | 0; |
michael@0 | 1743 | }, |
michael@0 | 1744 | isPassthrough: function DeviceRgbCS_isPassthrough(bits) { |
michael@0 | 1745 | return bits == 8; |
michael@0 | 1746 | }, |
michael@0 | 1747 | fillRgb: ColorSpace.prototype.fillRgb, |
michael@0 | 1748 | isDefaultDecode: function DeviceRgbCS_isDefaultDecode(decodeMap) { |
michael@0 | 1749 | return ColorSpace.isDefaultDecode(decodeMap, this.numComps); |
michael@0 | 1750 | }, |
michael@0 | 1751 | usesZeroToOneRange: true |
michael@0 | 1752 | }; |
michael@0 | 1753 | return DeviceRgbCS; |
michael@0 | 1754 | })(); |
michael@0 | 1755 | |
michael@0 | 1756 | var DeviceCmykCS = (function DeviceCmykCSClosure() { |
michael@0 | 1757 | // The coefficients below was found using numerical analysis: the method of |
michael@0 | 1758 | // steepest descent for the sum((f_i - color_value_i)^2) for r/g/b colors, |
michael@0 | 1759 | // where color_value is the tabular value from the table of sampled RGB colors |
michael@0 | 1760 | // from CMYK US Web Coated (SWOP) colorspace, and f_i is the corresponding |
michael@0 | 1761 | // CMYK color conversion using the estimation below: |
michael@0 | 1762 | // f(A, B,.. N) = Acc+Bcm+Ccy+Dck+c+Fmm+Gmy+Hmk+Im+Jyy+Kyk+Ly+Mkk+Nk+255 |
michael@0 | 1763 | function convertToRgb(src, srcOffset, srcScale, dest, destOffset) { |
michael@0 | 1764 | var c = src[srcOffset + 0] * srcScale; |
michael@0 | 1765 | var m = src[srcOffset + 1] * srcScale; |
michael@0 | 1766 | var y = src[srcOffset + 2] * srcScale; |
michael@0 | 1767 | var k = src[srcOffset + 3] * srcScale; |
michael@0 | 1768 | |
michael@0 | 1769 | var r = |
michael@0 | 1770 | (c * (-4.387332384609988 * c + 54.48615194189176 * m + |
michael@0 | 1771 | 18.82290502165302 * y + 212.25662451639585 * k + |
michael@0 | 1772 | -285.2331026137004) + |
michael@0 | 1773 | m * (1.7149763477362134 * m - 5.6096736904047315 * y + |
michael@0 | 1774 | -17.873870861415444 * k - 5.497006427196366) + |
michael@0 | 1775 | y * (-2.5217340131683033 * y - 21.248923337353073 * k + |
michael@0 | 1776 | 17.5119270841813) + |
michael@0 | 1777 | k * (-21.86122147463605 * k - 189.48180835922747) + 255) | 0; |
michael@0 | 1778 | var g = |
michael@0 | 1779 | (c * (8.841041422036149 * c + 60.118027045597366 * m + |
michael@0 | 1780 | 6.871425592049007 * y + 31.159100130055922 * k + |
michael@0 | 1781 | -79.2970844816548) + |
michael@0 | 1782 | m * (-15.310361306967817 * m + 17.575251261109482 * y + |
michael@0 | 1783 | 131.35250912493976 * k - 190.9453302588951) + |
michael@0 | 1784 | y * (4.444339102852739 * y + 9.8632861493405 * k - 24.86741582555878) + |
michael@0 | 1785 | k * (-20.737325471181034 * k - 187.80453709719578) + 255) | 0; |
michael@0 | 1786 | var b = |
michael@0 | 1787 | (c * (0.8842522430003296 * c + 8.078677503112928 * m + |
michael@0 | 1788 | 30.89978309703729 * y - 0.23883238689178934 * k + |
michael@0 | 1789 | -14.183576799673286) + |
michael@0 | 1790 | m * (10.49593273432072 * m + 63.02378494754052 * y + |
michael@0 | 1791 | 50.606957656360734 * k - 112.23884253719248) + |
michael@0 | 1792 | y * (0.03296041114873217 * y + 115.60384449646641 * k + |
michael@0 | 1793 | -193.58209356861505) + |
michael@0 | 1794 | k * (-22.33816807309886 * k - 180.12613974708367) + 255) | 0; |
michael@0 | 1795 | |
michael@0 | 1796 | dest[destOffset] = r > 255 ? 255 : r < 0 ? 0 : r; |
michael@0 | 1797 | dest[destOffset + 1] = g > 255 ? 255 : g < 0 ? 0 : g; |
michael@0 | 1798 | dest[destOffset + 2] = b > 255 ? 255 : b < 0 ? 0 : b; |
michael@0 | 1799 | } |
michael@0 | 1800 | |
michael@0 | 1801 | function DeviceCmykCS() { |
michael@0 | 1802 | this.name = 'DeviceCMYK'; |
michael@0 | 1803 | this.numComps = 4; |
michael@0 | 1804 | this.defaultColor = new Float32Array([0, 0, 0, 1]); |
michael@0 | 1805 | } |
michael@0 | 1806 | DeviceCmykCS.prototype = { |
michael@0 | 1807 | getRgb: ColorSpace.prototype.getRgb, |
michael@0 | 1808 | getRgbItem: function DeviceCmykCS_getRgbItem(src, srcOffset, |
michael@0 | 1809 | dest, destOffset) { |
michael@0 | 1810 | convertToRgb(src, srcOffset, 1, dest, destOffset); |
michael@0 | 1811 | }, |
michael@0 | 1812 | getRgbBuffer: function DeviceCmykCS_getRgbBuffer(src, srcOffset, count, |
michael@0 | 1813 | dest, destOffset, bits, |
michael@0 | 1814 | alpha01) { |
michael@0 | 1815 | var scale = 1 / ((1 << bits) - 1); |
michael@0 | 1816 | for (var i = 0; i < count; i++) { |
michael@0 | 1817 | convertToRgb(src, srcOffset, scale, dest, destOffset); |
michael@0 | 1818 | srcOffset += 4; |
michael@0 | 1819 | destOffset += 3 + alpha01; |
michael@0 | 1820 | } |
michael@0 | 1821 | }, |
michael@0 | 1822 | getOutputLength: function DeviceCmykCS_getOutputLength(inputLength, |
michael@0 | 1823 | alpha01) { |
michael@0 | 1824 | return (inputLength / 4 * (3 + alpha01)) | 0; |
michael@0 | 1825 | }, |
michael@0 | 1826 | isPassthrough: ColorSpace.prototype.isPassthrough, |
michael@0 | 1827 | fillRgb: ColorSpace.prototype.fillRgb, |
michael@0 | 1828 | isDefaultDecode: function DeviceCmykCS_isDefaultDecode(decodeMap) { |
michael@0 | 1829 | return ColorSpace.isDefaultDecode(decodeMap, this.numComps); |
michael@0 | 1830 | }, |
michael@0 | 1831 | usesZeroToOneRange: true |
michael@0 | 1832 | }; |
michael@0 | 1833 | |
michael@0 | 1834 | return DeviceCmykCS; |
michael@0 | 1835 | })(); |
michael@0 | 1836 | |
michael@0 | 1837 | // |
michael@0 | 1838 | // CalGrayCS: Based on "PDF Reference, Sixth Ed", p.245 |
michael@0 | 1839 | // |
michael@0 | 1840 | var CalGrayCS = (function CalGrayCSClosure() { |
michael@0 | 1841 | function CalGrayCS(whitePoint, blackPoint, gamma) { |
michael@0 | 1842 | this.name = 'CalGray'; |
michael@0 | 1843 | this.numComps = 1; |
michael@0 | 1844 | this.defaultColor = new Float32Array([0]); |
michael@0 | 1845 | |
michael@0 | 1846 | if (!whitePoint) { |
michael@0 | 1847 | error('WhitePoint missing - required for color space CalGray'); |
michael@0 | 1848 | } |
michael@0 | 1849 | blackPoint = blackPoint || [0, 0, 0]; |
michael@0 | 1850 | gamma = gamma || 1; |
michael@0 | 1851 | |
michael@0 | 1852 | // Translate arguments to spec variables. |
michael@0 | 1853 | this.XW = whitePoint[0]; |
michael@0 | 1854 | this.YW = whitePoint[1]; |
michael@0 | 1855 | this.ZW = whitePoint[2]; |
michael@0 | 1856 | |
michael@0 | 1857 | this.XB = blackPoint[0]; |
michael@0 | 1858 | this.YB = blackPoint[1]; |
michael@0 | 1859 | this.ZB = blackPoint[2]; |
michael@0 | 1860 | |
michael@0 | 1861 | this.G = gamma; |
michael@0 | 1862 | |
michael@0 | 1863 | // Validate variables as per spec. |
michael@0 | 1864 | if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) { |
michael@0 | 1865 | error('Invalid WhitePoint components for ' + this.name + |
michael@0 | 1866 | ', no fallback available'); |
michael@0 | 1867 | } |
michael@0 | 1868 | |
michael@0 | 1869 | if (this.XB < 0 || this.YB < 0 || this.ZB < 0) { |
michael@0 | 1870 | info('Invalid BlackPoint for ' + this.name + ', falling back to default'); |
michael@0 | 1871 | this.XB = this.YB = this.ZB = 0; |
michael@0 | 1872 | } |
michael@0 | 1873 | |
michael@0 | 1874 | if (this.XB !== 0 || this.YB !== 0 || this.ZB !== 0) { |
michael@0 | 1875 | warn(this.name + ', BlackPoint: XB: ' + this.XB + ', YB: ' + this.YB + |
michael@0 | 1876 | ', ZB: ' + this.ZB + ', only default values are supported.'); |
michael@0 | 1877 | } |
michael@0 | 1878 | |
michael@0 | 1879 | if (this.G < 1) { |
michael@0 | 1880 | info('Invalid Gamma: ' + this.G + ' for ' + this.name + |
michael@0 | 1881 | ', falling back to default'); |
michael@0 | 1882 | this.G = 1; |
michael@0 | 1883 | } |
michael@0 | 1884 | } |
michael@0 | 1885 | |
michael@0 | 1886 | function convertToRgb(cs, src, srcOffset, dest, destOffset, scale) { |
michael@0 | 1887 | // A represents a gray component of a calibrated gray space. |
michael@0 | 1888 | // A <---> AG in the spec |
michael@0 | 1889 | var A = src[srcOffset] * scale; |
michael@0 | 1890 | var AG = Math.pow(A, cs.G); |
michael@0 | 1891 | |
michael@0 | 1892 | // Computes intermediate variables M, L, N as per spec. |
michael@0 | 1893 | // Except if other than default BlackPoint values are used. |
michael@0 | 1894 | var M = cs.XW * AG; |
michael@0 | 1895 | var L = cs.YW * AG; |
michael@0 | 1896 | var N = cs.ZW * AG; |
michael@0 | 1897 | |
michael@0 | 1898 | // Decode XYZ, as per spec. |
michael@0 | 1899 | var X = M; |
michael@0 | 1900 | var Y = L; |
michael@0 | 1901 | var Z = N; |
michael@0 | 1902 | |
michael@0 | 1903 | // http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html, Ch 4. |
michael@0 | 1904 | // This yields values in range [0, 100]. |
michael@0 | 1905 | var Lstar = Math.max(116 * Math.pow(Y, 1 / 3) - 16, 0); |
michael@0 | 1906 | |
michael@0 | 1907 | // Convert values to rgb range [0, 255]. |
michael@0 | 1908 | dest[destOffset] = Lstar * 255 / 100; |
michael@0 | 1909 | dest[destOffset + 1] = Lstar * 255 / 100; |
michael@0 | 1910 | dest[destOffset + 2] = Lstar * 255 / 100; |
michael@0 | 1911 | } |
michael@0 | 1912 | |
michael@0 | 1913 | CalGrayCS.prototype = { |
michael@0 | 1914 | getRgb: ColorSpace.prototype.getRgb, |
michael@0 | 1915 | getRgbItem: function CalGrayCS_getRgbItem(src, srcOffset, |
michael@0 | 1916 | dest, destOffset) { |
michael@0 | 1917 | convertToRgb(this, src, srcOffset, dest, destOffset, 1); |
michael@0 | 1918 | }, |
michael@0 | 1919 | getRgbBuffer: function CalGrayCS_getRgbBuffer(src, srcOffset, count, |
michael@0 | 1920 | dest, destOffset, bits, |
michael@0 | 1921 | alpha01) { |
michael@0 | 1922 | var scale = 1 / ((1 << bits) - 1); |
michael@0 | 1923 | |
michael@0 | 1924 | for (var i = 0; i < count; ++i) { |
michael@0 | 1925 | convertToRgb(this, src, srcOffset, dest, destOffset, scale); |
michael@0 | 1926 | srcOffset += 1; |
michael@0 | 1927 | destOffset += 3 + alpha01; |
michael@0 | 1928 | } |
michael@0 | 1929 | }, |
michael@0 | 1930 | getOutputLength: function CalGrayCS_getOutputLength(inputLength, alpha01) { |
michael@0 | 1931 | return inputLength * (3 + alpha01); |
michael@0 | 1932 | }, |
michael@0 | 1933 | isPassthrough: ColorSpace.prototype.isPassthrough, |
michael@0 | 1934 | fillRgb: ColorSpace.prototype.fillRgb, |
michael@0 | 1935 | isDefaultDecode: function CalGrayCS_isDefaultDecode(decodeMap) { |
michael@0 | 1936 | return ColorSpace.isDefaultDecode(decodeMap, this.numComps); |
michael@0 | 1937 | }, |
michael@0 | 1938 | usesZeroToOneRange: true |
michael@0 | 1939 | }; |
michael@0 | 1940 | return CalGrayCS; |
michael@0 | 1941 | })(); |
michael@0 | 1942 | |
michael@0 | 1943 | // |
michael@0 | 1944 | // LabCS: Based on "PDF Reference, Sixth Ed", p.250 |
michael@0 | 1945 | // |
michael@0 | 1946 | var LabCS = (function LabCSClosure() { |
michael@0 | 1947 | function LabCS(whitePoint, blackPoint, range) { |
michael@0 | 1948 | this.name = 'Lab'; |
michael@0 | 1949 | this.numComps = 3; |
michael@0 | 1950 | this.defaultColor = new Float32Array([0, 0, 0]); |
michael@0 | 1951 | |
michael@0 | 1952 | if (!whitePoint) { |
michael@0 | 1953 | error('WhitePoint missing - required for color space Lab'); |
michael@0 | 1954 | } |
michael@0 | 1955 | blackPoint = blackPoint || [0, 0, 0]; |
michael@0 | 1956 | range = range || [-100, 100, -100, 100]; |
michael@0 | 1957 | |
michael@0 | 1958 | // Translate args to spec variables |
michael@0 | 1959 | this.XW = whitePoint[0]; |
michael@0 | 1960 | this.YW = whitePoint[1]; |
michael@0 | 1961 | this.ZW = whitePoint[2]; |
michael@0 | 1962 | this.amin = range[0]; |
michael@0 | 1963 | this.amax = range[1]; |
michael@0 | 1964 | this.bmin = range[2]; |
michael@0 | 1965 | this.bmax = range[3]; |
michael@0 | 1966 | |
michael@0 | 1967 | // These are here just for completeness - the spec doesn't offer any |
michael@0 | 1968 | // formulas that use BlackPoint in Lab |
michael@0 | 1969 | this.XB = blackPoint[0]; |
michael@0 | 1970 | this.YB = blackPoint[1]; |
michael@0 | 1971 | this.ZB = blackPoint[2]; |
michael@0 | 1972 | |
michael@0 | 1973 | // Validate vars as per spec |
michael@0 | 1974 | if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) { |
michael@0 | 1975 | error('Invalid WhitePoint components, no fallback available'); |
michael@0 | 1976 | } |
michael@0 | 1977 | |
michael@0 | 1978 | if (this.XB < 0 || this.YB < 0 || this.ZB < 0) { |
michael@0 | 1979 | info('Invalid BlackPoint, falling back to default'); |
michael@0 | 1980 | this.XB = this.YB = this.ZB = 0; |
michael@0 | 1981 | } |
michael@0 | 1982 | |
michael@0 | 1983 | if (this.amin > this.amax || this.bmin > this.bmax) { |
michael@0 | 1984 | info('Invalid Range, falling back to defaults'); |
michael@0 | 1985 | this.amin = -100; |
michael@0 | 1986 | this.amax = 100; |
michael@0 | 1987 | this.bmin = -100; |
michael@0 | 1988 | this.bmax = 100; |
michael@0 | 1989 | } |
michael@0 | 1990 | } |
michael@0 | 1991 | |
michael@0 | 1992 | // Function g(x) from spec |
michael@0 | 1993 | function fn_g(x) { |
michael@0 | 1994 | if (x >= 6 / 29) { |
michael@0 | 1995 | return x * x * x; |
michael@0 | 1996 | } else { |
michael@0 | 1997 | return (108 / 841) * (x - 4 / 29); |
michael@0 | 1998 | } |
michael@0 | 1999 | } |
michael@0 | 2000 | |
michael@0 | 2001 | function decode(value, high1, low2, high2) { |
michael@0 | 2002 | return low2 + (value) * (high2 - low2) / (high1); |
michael@0 | 2003 | } |
michael@0 | 2004 | |
michael@0 | 2005 | // If decoding is needed maxVal should be 2^bits per component - 1. |
michael@0 | 2006 | function convertToRgb(cs, src, srcOffset, maxVal, dest, destOffset) { |
michael@0 | 2007 | // XXX: Lab input is in the range of [0, 100], [amin, amax], [bmin, bmax] |
michael@0 | 2008 | // not the usual [0, 1]. If a command like setFillColor is used the src |
michael@0 | 2009 | // values will already be within the correct range. However, if we are |
michael@0 | 2010 | // converting an image we have to map the values to the correct range given |
michael@0 | 2011 | // above. |
michael@0 | 2012 | // Ls,as,bs <---> L*,a*,b* in the spec |
michael@0 | 2013 | var Ls = src[srcOffset]; |
michael@0 | 2014 | var as = src[srcOffset + 1]; |
michael@0 | 2015 | var bs = src[srcOffset + 2]; |
michael@0 | 2016 | if (maxVal !== false) { |
michael@0 | 2017 | Ls = decode(Ls, maxVal, 0, 100); |
michael@0 | 2018 | as = decode(as, maxVal, cs.amin, cs.amax); |
michael@0 | 2019 | bs = decode(bs, maxVal, cs.bmin, cs.bmax); |
michael@0 | 2020 | } |
michael@0 | 2021 | |
michael@0 | 2022 | // Adjust limits of 'as' and 'bs' |
michael@0 | 2023 | as = as > cs.amax ? cs.amax : as < cs.amin ? cs.amin : as; |
michael@0 | 2024 | bs = bs > cs.bmax ? cs.bmax : bs < cs.bmin ? cs.bmin : bs; |
michael@0 | 2025 | |
michael@0 | 2026 | // Computes intermediate variables X,Y,Z as per spec |
michael@0 | 2027 | var M = (Ls + 16) / 116; |
michael@0 | 2028 | var L = M + (as / 500); |
michael@0 | 2029 | var N = M - (bs / 200); |
michael@0 | 2030 | |
michael@0 | 2031 | var X = cs.XW * fn_g(L); |
michael@0 | 2032 | var Y = cs.YW * fn_g(M); |
michael@0 | 2033 | var Z = cs.ZW * fn_g(N); |
michael@0 | 2034 | |
michael@0 | 2035 | var r, g, b; |
michael@0 | 2036 | // Using different conversions for D50 and D65 white points, |
michael@0 | 2037 | // per http://www.color.org/srgb.pdf |
michael@0 | 2038 | if (cs.ZW < 1) { |
michael@0 | 2039 | // Assuming D50 (X=0.9642, Y=1.00, Z=0.8249) |
michael@0 | 2040 | r = X * 3.1339 + Y * -1.6170 + Z * -0.4906; |
michael@0 | 2041 | g = X * -0.9785 + Y * 1.9160 + Z * 0.0333; |
michael@0 | 2042 | b = X * 0.0720 + Y * -0.2290 + Z * 1.4057; |
michael@0 | 2043 | } else { |
michael@0 | 2044 | // Assuming D65 (X=0.9505, Y=1.00, Z=1.0888) |
michael@0 | 2045 | r = X * 3.2406 + Y * -1.5372 + Z * -0.4986; |
michael@0 | 2046 | g = X * -0.9689 + Y * 1.8758 + Z * 0.0415; |
michael@0 | 2047 | b = X * 0.0557 + Y * -0.2040 + Z * 1.0570; |
michael@0 | 2048 | } |
michael@0 | 2049 | // clamp color values to [0,1] range then convert to [0,255] range. |
michael@0 | 2050 | dest[destOffset] = r <= 0 ? 0 : r >= 1 ? 255 : Math.sqrt(r) * 255 | 0; |
michael@0 | 2051 | dest[destOffset + 1] = g <= 0 ? 0 : g >= 1 ? 255 : Math.sqrt(g) * 255 | 0; |
michael@0 | 2052 | dest[destOffset + 2] = b <= 0 ? 0 : b >= 1 ? 255 : Math.sqrt(b) * 255 | 0; |
michael@0 | 2053 | } |
michael@0 | 2054 | |
michael@0 | 2055 | LabCS.prototype = { |
michael@0 | 2056 | getRgb: ColorSpace.prototype.getRgb, |
michael@0 | 2057 | getRgbItem: function LabCS_getRgbItem(src, srcOffset, dest, destOffset) { |
michael@0 | 2058 | convertToRgb(this, src, srcOffset, false, dest, destOffset); |
michael@0 | 2059 | }, |
michael@0 | 2060 | getRgbBuffer: function LabCS_getRgbBuffer(src, srcOffset, count, |
michael@0 | 2061 | dest, destOffset, bits, |
michael@0 | 2062 | alpha01) { |
michael@0 | 2063 | var maxVal = (1 << bits) - 1; |
michael@0 | 2064 | for (var i = 0; i < count; i++) { |
michael@0 | 2065 | convertToRgb(this, src, srcOffset, maxVal, dest, destOffset); |
michael@0 | 2066 | srcOffset += 3; |
michael@0 | 2067 | destOffset += 3 + alpha01; |
michael@0 | 2068 | } |
michael@0 | 2069 | }, |
michael@0 | 2070 | getOutputLength: function LabCS_getOutputLength(inputLength, alpha01) { |
michael@0 | 2071 | return (inputLength * (3 + alpha01) / 3) | 0; |
michael@0 | 2072 | }, |
michael@0 | 2073 | isPassthrough: ColorSpace.prototype.isPassthrough, |
michael@0 | 2074 | isDefaultDecode: function LabCS_isDefaultDecode(decodeMap) { |
michael@0 | 2075 | // XXX: Decoding is handled with the lab conversion because of the strange |
michael@0 | 2076 | // ranges that are used. |
michael@0 | 2077 | return true; |
michael@0 | 2078 | }, |
michael@0 | 2079 | usesZeroToOneRange: false |
michael@0 | 2080 | }; |
michael@0 | 2081 | return LabCS; |
michael@0 | 2082 | })(); |
michael@0 | 2083 | |
michael@0 | 2084 | |
michael@0 | 2085 | |
michael@0 | 2086 | var PDFFunction = (function PDFFunctionClosure() { |
michael@0 | 2087 | var CONSTRUCT_SAMPLED = 0; |
michael@0 | 2088 | var CONSTRUCT_INTERPOLATED = 2; |
michael@0 | 2089 | var CONSTRUCT_STICHED = 3; |
michael@0 | 2090 | var CONSTRUCT_POSTSCRIPT = 4; |
michael@0 | 2091 | |
michael@0 | 2092 | return { |
michael@0 | 2093 | getSampleArray: function PDFFunction_getSampleArray(size, outputSize, bps, |
michael@0 | 2094 | str) { |
michael@0 | 2095 | var i, ii; |
michael@0 | 2096 | var length = 1; |
michael@0 | 2097 | for (i = 0, ii = size.length; i < ii; i++) { |
michael@0 | 2098 | length *= size[i]; |
michael@0 | 2099 | } |
michael@0 | 2100 | length *= outputSize; |
michael@0 | 2101 | |
michael@0 | 2102 | var array = []; |
michael@0 | 2103 | var codeSize = 0; |
michael@0 | 2104 | var codeBuf = 0; |
michael@0 | 2105 | // 32 is a valid bps so shifting won't work |
michael@0 | 2106 | var sampleMul = 1.0 / (Math.pow(2.0, bps) - 1); |
michael@0 | 2107 | |
michael@0 | 2108 | var strBytes = str.getBytes((length * bps + 7) / 8); |
michael@0 | 2109 | var strIdx = 0; |
michael@0 | 2110 | for (i = 0; i < length; i++) { |
michael@0 | 2111 | while (codeSize < bps) { |
michael@0 | 2112 | codeBuf <<= 8; |
michael@0 | 2113 | codeBuf |= strBytes[strIdx++]; |
michael@0 | 2114 | codeSize += 8; |
michael@0 | 2115 | } |
michael@0 | 2116 | codeSize -= bps; |
michael@0 | 2117 | array.push((codeBuf >> codeSize) * sampleMul); |
michael@0 | 2118 | codeBuf &= (1 << codeSize) - 1; |
michael@0 | 2119 | } |
michael@0 | 2120 | return array; |
michael@0 | 2121 | }, |
michael@0 | 2122 | |
michael@0 | 2123 | getIR: function PDFFunction_getIR(xref, fn) { |
michael@0 | 2124 | var dict = fn.dict; |
michael@0 | 2125 | if (!dict) { |
michael@0 | 2126 | dict = fn; |
michael@0 | 2127 | } |
michael@0 | 2128 | |
michael@0 | 2129 | var types = [this.constructSampled, |
michael@0 | 2130 | null, |
michael@0 | 2131 | this.constructInterpolated, |
michael@0 | 2132 | this.constructStiched, |
michael@0 | 2133 | this.constructPostScript]; |
michael@0 | 2134 | |
michael@0 | 2135 | var typeNum = dict.get('FunctionType'); |
michael@0 | 2136 | var typeFn = types[typeNum]; |
michael@0 | 2137 | if (!typeFn) { |
michael@0 | 2138 | error('Unknown type of function'); |
michael@0 | 2139 | } |
michael@0 | 2140 | |
michael@0 | 2141 | return typeFn.call(this, fn, dict, xref); |
michael@0 | 2142 | }, |
michael@0 | 2143 | |
michael@0 | 2144 | fromIR: function PDFFunction_fromIR(IR) { |
michael@0 | 2145 | var type = IR[0]; |
michael@0 | 2146 | switch (type) { |
michael@0 | 2147 | case CONSTRUCT_SAMPLED: |
michael@0 | 2148 | return this.constructSampledFromIR(IR); |
michael@0 | 2149 | case CONSTRUCT_INTERPOLATED: |
michael@0 | 2150 | return this.constructInterpolatedFromIR(IR); |
michael@0 | 2151 | case CONSTRUCT_STICHED: |
michael@0 | 2152 | return this.constructStichedFromIR(IR); |
michael@0 | 2153 | //case CONSTRUCT_POSTSCRIPT: |
michael@0 | 2154 | default: |
michael@0 | 2155 | return this.constructPostScriptFromIR(IR); |
michael@0 | 2156 | } |
michael@0 | 2157 | }, |
michael@0 | 2158 | |
michael@0 | 2159 | parse: function PDFFunction_parse(xref, fn) { |
michael@0 | 2160 | var IR = this.getIR(xref, fn); |
michael@0 | 2161 | return this.fromIR(IR); |
michael@0 | 2162 | }, |
michael@0 | 2163 | |
michael@0 | 2164 | constructSampled: function PDFFunction_constructSampled(str, dict) { |
michael@0 | 2165 | function toMultiArray(arr) { |
michael@0 | 2166 | var inputLength = arr.length; |
michael@0 | 2167 | var out = []; |
michael@0 | 2168 | var index = 0; |
michael@0 | 2169 | for (var i = 0; i < inputLength; i += 2) { |
michael@0 | 2170 | out[index] = [arr[i], arr[i + 1]]; |
michael@0 | 2171 | ++index; |
michael@0 | 2172 | } |
michael@0 | 2173 | return out; |
michael@0 | 2174 | } |
michael@0 | 2175 | var domain = dict.get('Domain'); |
michael@0 | 2176 | var range = dict.get('Range'); |
michael@0 | 2177 | |
michael@0 | 2178 | if (!domain || !range) { |
michael@0 | 2179 | error('No domain or range'); |
michael@0 | 2180 | } |
michael@0 | 2181 | |
michael@0 | 2182 | var inputSize = domain.length / 2; |
michael@0 | 2183 | var outputSize = range.length / 2; |
michael@0 | 2184 | |
michael@0 | 2185 | domain = toMultiArray(domain); |
michael@0 | 2186 | range = toMultiArray(range); |
michael@0 | 2187 | |
michael@0 | 2188 | var size = dict.get('Size'); |
michael@0 | 2189 | var bps = dict.get('BitsPerSample'); |
michael@0 | 2190 | var order = dict.get('Order') || 1; |
michael@0 | 2191 | if (order !== 1) { |
michael@0 | 2192 | // No description how cubic spline interpolation works in PDF32000:2008 |
michael@0 | 2193 | // As in poppler, ignoring order, linear interpolation may work as good |
michael@0 | 2194 | info('No support for cubic spline interpolation: ' + order); |
michael@0 | 2195 | } |
michael@0 | 2196 | |
michael@0 | 2197 | var encode = dict.get('Encode'); |
michael@0 | 2198 | if (!encode) { |
michael@0 | 2199 | encode = []; |
michael@0 | 2200 | for (var i = 0; i < inputSize; ++i) { |
michael@0 | 2201 | encode.push(0); |
michael@0 | 2202 | encode.push(size[i] - 1); |
michael@0 | 2203 | } |
michael@0 | 2204 | } |
michael@0 | 2205 | encode = toMultiArray(encode); |
michael@0 | 2206 | |
michael@0 | 2207 | var decode = dict.get('Decode'); |
michael@0 | 2208 | if (!decode) { |
michael@0 | 2209 | decode = range; |
michael@0 | 2210 | } else { |
michael@0 | 2211 | decode = toMultiArray(decode); |
michael@0 | 2212 | } |
michael@0 | 2213 | |
michael@0 | 2214 | var samples = this.getSampleArray(size, outputSize, bps, str); |
michael@0 | 2215 | |
michael@0 | 2216 | return [ |
michael@0 | 2217 | CONSTRUCT_SAMPLED, inputSize, domain, encode, decode, samples, size, |
michael@0 | 2218 | outputSize, Math.pow(2, bps) - 1, range |
michael@0 | 2219 | ]; |
michael@0 | 2220 | }, |
michael@0 | 2221 | |
michael@0 | 2222 | constructSampledFromIR: function PDFFunction_constructSampledFromIR(IR) { |
michael@0 | 2223 | // See chapter 3, page 109 of the PDF reference |
michael@0 | 2224 | function interpolate(x, xmin, xmax, ymin, ymax) { |
michael@0 | 2225 | return ymin + ((x - xmin) * ((ymax - ymin) / (xmax - xmin))); |
michael@0 | 2226 | } |
michael@0 | 2227 | |
michael@0 | 2228 | return function constructSampledFromIRResult(args) { |
michael@0 | 2229 | // See chapter 3, page 110 of the PDF reference. |
michael@0 | 2230 | var m = IR[1]; |
michael@0 | 2231 | var domain = IR[2]; |
michael@0 | 2232 | var encode = IR[3]; |
michael@0 | 2233 | var decode = IR[4]; |
michael@0 | 2234 | var samples = IR[5]; |
michael@0 | 2235 | var size = IR[6]; |
michael@0 | 2236 | var n = IR[7]; |
michael@0 | 2237 | //var mask = IR[8]; |
michael@0 | 2238 | var range = IR[9]; |
michael@0 | 2239 | |
michael@0 | 2240 | if (m != args.length) { |
michael@0 | 2241 | error('Incorrect number of arguments: ' + m + ' != ' + |
michael@0 | 2242 | args.length); |
michael@0 | 2243 | } |
michael@0 | 2244 | |
michael@0 | 2245 | var x = args; |
michael@0 | 2246 | |
michael@0 | 2247 | // Building the cube vertices: its part and sample index |
michael@0 | 2248 | // http://rjwagner49.com/Mathematics/Interpolation.pdf |
michael@0 | 2249 | var cubeVertices = 1 << m; |
michael@0 | 2250 | var cubeN = new Float64Array(cubeVertices); |
michael@0 | 2251 | var cubeVertex = new Uint32Array(cubeVertices); |
michael@0 | 2252 | var i, j; |
michael@0 | 2253 | for (j = 0; j < cubeVertices; j++) { |
michael@0 | 2254 | cubeN[j] = 1; |
michael@0 | 2255 | } |
michael@0 | 2256 | |
michael@0 | 2257 | var k = n, pos = 1; |
michael@0 | 2258 | // Map x_i to y_j for 0 <= i < m using the sampled function. |
michael@0 | 2259 | for (i = 0; i < m; ++i) { |
michael@0 | 2260 | // x_i' = min(max(x_i, Domain_2i), Domain_2i+1) |
michael@0 | 2261 | var domain_2i = domain[i][0]; |
michael@0 | 2262 | var domain_2i_1 = domain[i][1]; |
michael@0 | 2263 | var xi = Math.min(Math.max(x[i], domain_2i), domain_2i_1); |
michael@0 | 2264 | |
michael@0 | 2265 | // e_i = Interpolate(x_i', Domain_2i, Domain_2i+1, |
michael@0 | 2266 | // Encode_2i, Encode_2i+1) |
michael@0 | 2267 | var e = interpolate(xi, domain_2i, domain_2i_1, |
michael@0 | 2268 | encode[i][0], encode[i][1]); |
michael@0 | 2269 | |
michael@0 | 2270 | // e_i' = min(max(e_i, 0), Size_i - 1) |
michael@0 | 2271 | var size_i = size[i]; |
michael@0 | 2272 | e = Math.min(Math.max(e, 0), size_i - 1); |
michael@0 | 2273 | |
michael@0 | 2274 | // Adjusting the cube: N and vertex sample index |
michael@0 | 2275 | var e0 = e < size_i - 1 ? Math.floor(e) : e - 1; // e1 = e0 + 1; |
michael@0 | 2276 | var n0 = e0 + 1 - e; // (e1 - e) / (e1 - e0); |
michael@0 | 2277 | var n1 = e - e0; // (e - e0) / (e1 - e0); |
michael@0 | 2278 | var offset0 = e0 * k; |
michael@0 | 2279 | var offset1 = offset0 + k; // e1 * k |
michael@0 | 2280 | for (j = 0; j < cubeVertices; j++) { |
michael@0 | 2281 | if (j & pos) { |
michael@0 | 2282 | cubeN[j] *= n1; |
michael@0 | 2283 | cubeVertex[j] += offset1; |
michael@0 | 2284 | } else { |
michael@0 | 2285 | cubeN[j] *= n0; |
michael@0 | 2286 | cubeVertex[j] += offset0; |
michael@0 | 2287 | } |
michael@0 | 2288 | } |
michael@0 | 2289 | |
michael@0 | 2290 | k *= size_i; |
michael@0 | 2291 | pos <<= 1; |
michael@0 | 2292 | } |
michael@0 | 2293 | |
michael@0 | 2294 | var y = new Float64Array(n); |
michael@0 | 2295 | for (j = 0; j < n; ++j) { |
michael@0 | 2296 | // Sum all cube vertices' samples portions |
michael@0 | 2297 | var rj = 0; |
michael@0 | 2298 | for (i = 0; i < cubeVertices; i++) { |
michael@0 | 2299 | rj += samples[cubeVertex[i] + j] * cubeN[i]; |
michael@0 | 2300 | } |
michael@0 | 2301 | |
michael@0 | 2302 | // r_j' = Interpolate(r_j, 0, 2^BitsPerSample - 1, |
michael@0 | 2303 | // Decode_2j, Decode_2j+1) |
michael@0 | 2304 | rj = interpolate(rj, 0, 1, decode[j][0], decode[j][1]); |
michael@0 | 2305 | |
michael@0 | 2306 | // y_j = min(max(r_j, range_2j), range_2j+1) |
michael@0 | 2307 | y[j] = Math.min(Math.max(rj, range[j][0]), range[j][1]); |
michael@0 | 2308 | } |
michael@0 | 2309 | |
michael@0 | 2310 | return y; |
michael@0 | 2311 | }; |
michael@0 | 2312 | }, |
michael@0 | 2313 | |
michael@0 | 2314 | constructInterpolated: function PDFFunction_constructInterpolated(str, |
michael@0 | 2315 | dict) { |
michael@0 | 2316 | var c0 = dict.get('C0') || [0]; |
michael@0 | 2317 | var c1 = dict.get('C1') || [1]; |
michael@0 | 2318 | var n = dict.get('N'); |
michael@0 | 2319 | |
michael@0 | 2320 | if (!isArray(c0) || !isArray(c1)) { |
michael@0 | 2321 | error('Illegal dictionary for interpolated function'); |
michael@0 | 2322 | } |
michael@0 | 2323 | |
michael@0 | 2324 | var length = c0.length; |
michael@0 | 2325 | var diff = []; |
michael@0 | 2326 | for (var i = 0; i < length; ++i) { |
michael@0 | 2327 | diff.push(c1[i] - c0[i]); |
michael@0 | 2328 | } |
michael@0 | 2329 | |
michael@0 | 2330 | return [CONSTRUCT_INTERPOLATED, c0, diff, n]; |
michael@0 | 2331 | }, |
michael@0 | 2332 | |
michael@0 | 2333 | constructInterpolatedFromIR: |
michael@0 | 2334 | function PDFFunction_constructInterpolatedFromIR(IR) { |
michael@0 | 2335 | var c0 = IR[1]; |
michael@0 | 2336 | var diff = IR[2]; |
michael@0 | 2337 | var n = IR[3]; |
michael@0 | 2338 | |
michael@0 | 2339 | var length = diff.length; |
michael@0 | 2340 | |
michael@0 | 2341 | return function constructInterpolatedFromIRResult(args) { |
michael@0 | 2342 | var x = n == 1 ? args[0] : Math.pow(args[0], n); |
michael@0 | 2343 | |
michael@0 | 2344 | var out = []; |
michael@0 | 2345 | for (var j = 0; j < length; ++j) { |
michael@0 | 2346 | out.push(c0[j] + (x * diff[j])); |
michael@0 | 2347 | } |
michael@0 | 2348 | |
michael@0 | 2349 | return out; |
michael@0 | 2350 | |
michael@0 | 2351 | }; |
michael@0 | 2352 | }, |
michael@0 | 2353 | |
michael@0 | 2354 | constructStiched: function PDFFunction_constructStiched(fn, dict, xref) { |
michael@0 | 2355 | var domain = dict.get('Domain'); |
michael@0 | 2356 | |
michael@0 | 2357 | if (!domain) { |
michael@0 | 2358 | error('No domain'); |
michael@0 | 2359 | } |
michael@0 | 2360 | |
michael@0 | 2361 | var inputSize = domain.length / 2; |
michael@0 | 2362 | if (inputSize != 1) { |
michael@0 | 2363 | error('Bad domain for stiched function'); |
michael@0 | 2364 | } |
michael@0 | 2365 | |
michael@0 | 2366 | var fnRefs = dict.get('Functions'); |
michael@0 | 2367 | var fns = []; |
michael@0 | 2368 | for (var i = 0, ii = fnRefs.length; i < ii; ++i) { |
michael@0 | 2369 | fns.push(PDFFunction.getIR(xref, xref.fetchIfRef(fnRefs[i]))); |
michael@0 | 2370 | } |
michael@0 | 2371 | |
michael@0 | 2372 | var bounds = dict.get('Bounds'); |
michael@0 | 2373 | var encode = dict.get('Encode'); |
michael@0 | 2374 | |
michael@0 | 2375 | return [CONSTRUCT_STICHED, domain, bounds, encode, fns]; |
michael@0 | 2376 | }, |
michael@0 | 2377 | |
michael@0 | 2378 | constructStichedFromIR: function PDFFunction_constructStichedFromIR(IR) { |
michael@0 | 2379 | var domain = IR[1]; |
michael@0 | 2380 | var bounds = IR[2]; |
michael@0 | 2381 | var encode = IR[3]; |
michael@0 | 2382 | var fnsIR = IR[4]; |
michael@0 | 2383 | var fns = []; |
michael@0 | 2384 | |
michael@0 | 2385 | for (var i = 0, ii = fnsIR.length; i < ii; i++) { |
michael@0 | 2386 | fns.push(PDFFunction.fromIR(fnsIR[i])); |
michael@0 | 2387 | } |
michael@0 | 2388 | |
michael@0 | 2389 | return function constructStichedFromIRResult(args) { |
michael@0 | 2390 | var clip = function constructStichedFromIRClip(v, min, max) { |
michael@0 | 2391 | if (v > max) { |
michael@0 | 2392 | v = max; |
michael@0 | 2393 | } else if (v < min) { |
michael@0 | 2394 | v = min; |
michael@0 | 2395 | } |
michael@0 | 2396 | return v; |
michael@0 | 2397 | }; |
michael@0 | 2398 | |
michael@0 | 2399 | // clip to domain |
michael@0 | 2400 | var v = clip(args[0], domain[0], domain[1]); |
michael@0 | 2401 | // calulate which bound the value is in |
michael@0 | 2402 | for (var i = 0, ii = bounds.length; i < ii; ++i) { |
michael@0 | 2403 | if (v < bounds[i]) { |
michael@0 | 2404 | break; |
michael@0 | 2405 | } |
michael@0 | 2406 | } |
michael@0 | 2407 | |
michael@0 | 2408 | // encode value into domain of function |
michael@0 | 2409 | var dmin = domain[0]; |
michael@0 | 2410 | if (i > 0) { |
michael@0 | 2411 | dmin = bounds[i - 1]; |
michael@0 | 2412 | } |
michael@0 | 2413 | var dmax = domain[1]; |
michael@0 | 2414 | if (i < bounds.length) { |
michael@0 | 2415 | dmax = bounds[i]; |
michael@0 | 2416 | } |
michael@0 | 2417 | |
michael@0 | 2418 | var rmin = encode[2 * i]; |
michael@0 | 2419 | var rmax = encode[2 * i + 1]; |
michael@0 | 2420 | |
michael@0 | 2421 | var v2 = rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin); |
michael@0 | 2422 | |
michael@0 | 2423 | // call the appropriate function |
michael@0 | 2424 | return fns[i]([v2]); |
michael@0 | 2425 | }; |
michael@0 | 2426 | }, |
michael@0 | 2427 | |
michael@0 | 2428 | constructPostScript: function PDFFunction_constructPostScript(fn, dict, |
michael@0 | 2429 | xref) { |
michael@0 | 2430 | var domain = dict.get('Domain'); |
michael@0 | 2431 | var range = dict.get('Range'); |
michael@0 | 2432 | |
michael@0 | 2433 | if (!domain) { |
michael@0 | 2434 | error('No domain.'); |
michael@0 | 2435 | } |
michael@0 | 2436 | |
michael@0 | 2437 | if (!range) { |
michael@0 | 2438 | error('No range.'); |
michael@0 | 2439 | } |
michael@0 | 2440 | |
michael@0 | 2441 | var lexer = new PostScriptLexer(fn); |
michael@0 | 2442 | var parser = new PostScriptParser(lexer); |
michael@0 | 2443 | var code = parser.parse(); |
michael@0 | 2444 | |
michael@0 | 2445 | return [CONSTRUCT_POSTSCRIPT, domain, range, code]; |
michael@0 | 2446 | }, |
michael@0 | 2447 | |
michael@0 | 2448 | constructPostScriptFromIR: function PDFFunction_constructPostScriptFromIR( |
michael@0 | 2449 | IR) { |
michael@0 | 2450 | var domain = IR[1]; |
michael@0 | 2451 | var range = IR[2]; |
michael@0 | 2452 | var code = IR[3]; |
michael@0 | 2453 | var numOutputs = range.length / 2; |
michael@0 | 2454 | var evaluator = new PostScriptEvaluator(code); |
michael@0 | 2455 | // Cache the values for a big speed up, the cache size is limited though |
michael@0 | 2456 | // since the number of possible values can be huge from a PS function. |
michael@0 | 2457 | var cache = new FunctionCache(); |
michael@0 | 2458 | return function constructPostScriptFromIRResult(args) { |
michael@0 | 2459 | var initialStack = []; |
michael@0 | 2460 | for (var i = 0, ii = (domain.length / 2); i < ii; ++i) { |
michael@0 | 2461 | initialStack.push(args[i]); |
michael@0 | 2462 | } |
michael@0 | 2463 | |
michael@0 | 2464 | var key = initialStack.join('_'); |
michael@0 | 2465 | if (cache.has(key)) { |
michael@0 | 2466 | return cache.get(key); |
michael@0 | 2467 | } |
michael@0 | 2468 | |
michael@0 | 2469 | var stack = evaluator.execute(initialStack); |
michael@0 | 2470 | var transformed = []; |
michael@0 | 2471 | for (i = numOutputs - 1; i >= 0; --i) { |
michael@0 | 2472 | var out = stack.pop(); |
michael@0 | 2473 | var rangeIndex = 2 * i; |
michael@0 | 2474 | if (out < range[rangeIndex]) { |
michael@0 | 2475 | out = range[rangeIndex]; |
michael@0 | 2476 | } else if (out > range[rangeIndex + 1]) { |
michael@0 | 2477 | out = range[rangeIndex + 1]; |
michael@0 | 2478 | } |
michael@0 | 2479 | transformed[i] = out; |
michael@0 | 2480 | } |
michael@0 | 2481 | cache.set(key, transformed); |
michael@0 | 2482 | return transformed; |
michael@0 | 2483 | }; |
michael@0 | 2484 | } |
michael@0 | 2485 | }; |
michael@0 | 2486 | })(); |
michael@0 | 2487 | |
michael@0 | 2488 | var FunctionCache = (function FunctionCacheClosure() { |
michael@0 | 2489 | // Of 10 PDF's with type4 functions the maxium number of distinct values seen |
michael@0 | 2490 | // was 256. This still may need some tweaking in the future though. |
michael@0 | 2491 | var MAX_CACHE_SIZE = 1024; |
michael@0 | 2492 | function FunctionCache() { |
michael@0 | 2493 | this.cache = {}; |
michael@0 | 2494 | this.total = 0; |
michael@0 | 2495 | } |
michael@0 | 2496 | FunctionCache.prototype = { |
michael@0 | 2497 | has: function FunctionCache_has(key) { |
michael@0 | 2498 | return key in this.cache; |
michael@0 | 2499 | }, |
michael@0 | 2500 | get: function FunctionCache_get(key) { |
michael@0 | 2501 | return this.cache[key]; |
michael@0 | 2502 | }, |
michael@0 | 2503 | set: function FunctionCache_set(key, value) { |
michael@0 | 2504 | if (this.total < MAX_CACHE_SIZE) { |
michael@0 | 2505 | this.cache[key] = value; |
michael@0 | 2506 | this.total++; |
michael@0 | 2507 | } |
michael@0 | 2508 | } |
michael@0 | 2509 | }; |
michael@0 | 2510 | return FunctionCache; |
michael@0 | 2511 | })(); |
michael@0 | 2512 | |
michael@0 | 2513 | var PostScriptStack = (function PostScriptStackClosure() { |
michael@0 | 2514 | var MAX_STACK_SIZE = 100; |
michael@0 | 2515 | function PostScriptStack(initialStack) { |
michael@0 | 2516 | this.stack = initialStack || []; |
michael@0 | 2517 | } |
michael@0 | 2518 | |
michael@0 | 2519 | PostScriptStack.prototype = { |
michael@0 | 2520 | push: function PostScriptStack_push(value) { |
michael@0 | 2521 | if (this.stack.length >= MAX_STACK_SIZE) { |
michael@0 | 2522 | error('PostScript function stack overflow.'); |
michael@0 | 2523 | } |
michael@0 | 2524 | this.stack.push(value); |
michael@0 | 2525 | }, |
michael@0 | 2526 | pop: function PostScriptStack_pop() { |
michael@0 | 2527 | if (this.stack.length <= 0) { |
michael@0 | 2528 | error('PostScript function stack underflow.'); |
michael@0 | 2529 | } |
michael@0 | 2530 | return this.stack.pop(); |
michael@0 | 2531 | }, |
michael@0 | 2532 | copy: function PostScriptStack_copy(n) { |
michael@0 | 2533 | if (this.stack.length + n >= MAX_STACK_SIZE) { |
michael@0 | 2534 | error('PostScript function stack overflow.'); |
michael@0 | 2535 | } |
michael@0 | 2536 | var stack = this.stack; |
michael@0 | 2537 | for (var i = stack.length - n, j = n - 1; j >= 0; j--, i++) { |
michael@0 | 2538 | stack.push(stack[i]); |
michael@0 | 2539 | } |
michael@0 | 2540 | }, |
michael@0 | 2541 | index: function PostScriptStack_index(n) { |
michael@0 | 2542 | this.push(this.stack[this.stack.length - n - 1]); |
michael@0 | 2543 | }, |
michael@0 | 2544 | // rotate the last n stack elements p times |
michael@0 | 2545 | roll: function PostScriptStack_roll(n, p) { |
michael@0 | 2546 | var stack = this.stack; |
michael@0 | 2547 | var l = stack.length - n; |
michael@0 | 2548 | var r = stack.length - 1, c = l + (p - Math.floor(p / n) * n), i, j, t; |
michael@0 | 2549 | for (i = l, j = r; i < j; i++, j--) { |
michael@0 | 2550 | t = stack[i]; stack[i] = stack[j]; stack[j] = t; |
michael@0 | 2551 | } |
michael@0 | 2552 | for (i = l, j = c - 1; i < j; i++, j--) { |
michael@0 | 2553 | t = stack[i]; stack[i] = stack[j]; stack[j] = t; |
michael@0 | 2554 | } |
michael@0 | 2555 | for (i = c, j = r; i < j; i++, j--) { |
michael@0 | 2556 | t = stack[i]; stack[i] = stack[j]; stack[j] = t; |
michael@0 | 2557 | } |
michael@0 | 2558 | } |
michael@0 | 2559 | }; |
michael@0 | 2560 | return PostScriptStack; |
michael@0 | 2561 | })(); |
michael@0 | 2562 | var PostScriptEvaluator = (function PostScriptEvaluatorClosure() { |
michael@0 | 2563 | function PostScriptEvaluator(operators) { |
michael@0 | 2564 | this.operators = operators; |
michael@0 | 2565 | } |
michael@0 | 2566 | PostScriptEvaluator.prototype = { |
michael@0 | 2567 | execute: function PostScriptEvaluator_execute(initialStack) { |
michael@0 | 2568 | var stack = new PostScriptStack(initialStack); |
michael@0 | 2569 | var counter = 0; |
michael@0 | 2570 | var operators = this.operators; |
michael@0 | 2571 | var length = operators.length; |
michael@0 | 2572 | var operator, a, b; |
michael@0 | 2573 | while (counter < length) { |
michael@0 | 2574 | operator = operators[counter++]; |
michael@0 | 2575 | if (typeof operator == 'number') { |
michael@0 | 2576 | // Operator is really an operand and should be pushed to the stack. |
michael@0 | 2577 | stack.push(operator); |
michael@0 | 2578 | continue; |
michael@0 | 2579 | } |
michael@0 | 2580 | switch (operator) { |
michael@0 | 2581 | // non standard ps operators |
michael@0 | 2582 | case 'jz': // jump if false |
michael@0 | 2583 | b = stack.pop(); |
michael@0 | 2584 | a = stack.pop(); |
michael@0 | 2585 | if (!a) { |
michael@0 | 2586 | counter = b; |
michael@0 | 2587 | } |
michael@0 | 2588 | break; |
michael@0 | 2589 | case 'j': // jump |
michael@0 | 2590 | a = stack.pop(); |
michael@0 | 2591 | counter = a; |
michael@0 | 2592 | break; |
michael@0 | 2593 | |
michael@0 | 2594 | // all ps operators in alphabetical order (excluding if/ifelse) |
michael@0 | 2595 | case 'abs': |
michael@0 | 2596 | a = stack.pop(); |
michael@0 | 2597 | stack.push(Math.abs(a)); |
michael@0 | 2598 | break; |
michael@0 | 2599 | case 'add': |
michael@0 | 2600 | b = stack.pop(); |
michael@0 | 2601 | a = stack.pop(); |
michael@0 | 2602 | stack.push(a + b); |
michael@0 | 2603 | break; |
michael@0 | 2604 | case 'and': |
michael@0 | 2605 | b = stack.pop(); |
michael@0 | 2606 | a = stack.pop(); |
michael@0 | 2607 | if (isBool(a) && isBool(b)) { |
michael@0 | 2608 | stack.push(a && b); |
michael@0 | 2609 | } else { |
michael@0 | 2610 | stack.push(a & b); |
michael@0 | 2611 | } |
michael@0 | 2612 | break; |
michael@0 | 2613 | case 'atan': |
michael@0 | 2614 | a = stack.pop(); |
michael@0 | 2615 | stack.push(Math.atan(a)); |
michael@0 | 2616 | break; |
michael@0 | 2617 | case 'bitshift': |
michael@0 | 2618 | b = stack.pop(); |
michael@0 | 2619 | a = stack.pop(); |
michael@0 | 2620 | if (a > 0) { |
michael@0 | 2621 | stack.push(a << b); |
michael@0 | 2622 | } else { |
michael@0 | 2623 | stack.push(a >> b); |
michael@0 | 2624 | } |
michael@0 | 2625 | break; |
michael@0 | 2626 | case 'ceiling': |
michael@0 | 2627 | a = stack.pop(); |
michael@0 | 2628 | stack.push(Math.ceil(a)); |
michael@0 | 2629 | break; |
michael@0 | 2630 | case 'copy': |
michael@0 | 2631 | a = stack.pop(); |
michael@0 | 2632 | stack.copy(a); |
michael@0 | 2633 | break; |
michael@0 | 2634 | case 'cos': |
michael@0 | 2635 | a = stack.pop(); |
michael@0 | 2636 | stack.push(Math.cos(a)); |
michael@0 | 2637 | break; |
michael@0 | 2638 | case 'cvi': |
michael@0 | 2639 | a = stack.pop() | 0; |
michael@0 | 2640 | stack.push(a); |
michael@0 | 2641 | break; |
michael@0 | 2642 | case 'cvr': |
michael@0 | 2643 | // noop |
michael@0 | 2644 | break; |
michael@0 | 2645 | case 'div': |
michael@0 | 2646 | b = stack.pop(); |
michael@0 | 2647 | a = stack.pop(); |
michael@0 | 2648 | stack.push(a / b); |
michael@0 | 2649 | break; |
michael@0 | 2650 | case 'dup': |
michael@0 | 2651 | stack.copy(1); |
michael@0 | 2652 | break; |
michael@0 | 2653 | case 'eq': |
michael@0 | 2654 | b = stack.pop(); |
michael@0 | 2655 | a = stack.pop(); |
michael@0 | 2656 | stack.push(a == b); |
michael@0 | 2657 | break; |
michael@0 | 2658 | case 'exch': |
michael@0 | 2659 | stack.roll(2, 1); |
michael@0 | 2660 | break; |
michael@0 | 2661 | case 'exp': |
michael@0 | 2662 | b = stack.pop(); |
michael@0 | 2663 | a = stack.pop(); |
michael@0 | 2664 | stack.push(Math.pow(a, b)); |
michael@0 | 2665 | break; |
michael@0 | 2666 | case 'false': |
michael@0 | 2667 | stack.push(false); |
michael@0 | 2668 | break; |
michael@0 | 2669 | case 'floor': |
michael@0 | 2670 | a = stack.pop(); |
michael@0 | 2671 | stack.push(Math.floor(a)); |
michael@0 | 2672 | break; |
michael@0 | 2673 | case 'ge': |
michael@0 | 2674 | b = stack.pop(); |
michael@0 | 2675 | a = stack.pop(); |
michael@0 | 2676 | stack.push(a >= b); |
michael@0 | 2677 | break; |
michael@0 | 2678 | case 'gt': |
michael@0 | 2679 | b = stack.pop(); |
michael@0 | 2680 | a = stack.pop(); |
michael@0 | 2681 | stack.push(a > b); |
michael@0 | 2682 | break; |
michael@0 | 2683 | case 'idiv': |
michael@0 | 2684 | b = stack.pop(); |
michael@0 | 2685 | a = stack.pop(); |
michael@0 | 2686 | stack.push((a / b) | 0); |
michael@0 | 2687 | break; |
michael@0 | 2688 | case 'index': |
michael@0 | 2689 | a = stack.pop(); |
michael@0 | 2690 | stack.index(a); |
michael@0 | 2691 | break; |
michael@0 | 2692 | case 'le': |
michael@0 | 2693 | b = stack.pop(); |
michael@0 | 2694 | a = stack.pop(); |
michael@0 | 2695 | stack.push(a <= b); |
michael@0 | 2696 | break; |
michael@0 | 2697 | case 'ln': |
michael@0 | 2698 | a = stack.pop(); |
michael@0 | 2699 | stack.push(Math.log(a)); |
michael@0 | 2700 | break; |
michael@0 | 2701 | case 'log': |
michael@0 | 2702 | a = stack.pop(); |
michael@0 | 2703 | stack.push(Math.log(a) / Math.LN10); |
michael@0 | 2704 | break; |
michael@0 | 2705 | case 'lt': |
michael@0 | 2706 | b = stack.pop(); |
michael@0 | 2707 | a = stack.pop(); |
michael@0 | 2708 | stack.push(a < b); |
michael@0 | 2709 | break; |
michael@0 | 2710 | case 'mod': |
michael@0 | 2711 | b = stack.pop(); |
michael@0 | 2712 | a = stack.pop(); |
michael@0 | 2713 | stack.push(a % b); |
michael@0 | 2714 | break; |
michael@0 | 2715 | case 'mul': |
michael@0 | 2716 | b = stack.pop(); |
michael@0 | 2717 | a = stack.pop(); |
michael@0 | 2718 | stack.push(a * b); |
michael@0 | 2719 | break; |
michael@0 | 2720 | case 'ne': |
michael@0 | 2721 | b = stack.pop(); |
michael@0 | 2722 | a = stack.pop(); |
michael@0 | 2723 | stack.push(a != b); |
michael@0 | 2724 | break; |
michael@0 | 2725 | case 'neg': |
michael@0 | 2726 | a = stack.pop(); |
michael@0 | 2727 | stack.push(-b); |
michael@0 | 2728 | break; |
michael@0 | 2729 | case 'not': |
michael@0 | 2730 | a = stack.pop(); |
michael@0 | 2731 | if (isBool(a) && isBool(b)) { |
michael@0 | 2732 | stack.push(a && b); |
michael@0 | 2733 | } else { |
michael@0 | 2734 | stack.push(a & b); |
michael@0 | 2735 | } |
michael@0 | 2736 | break; |
michael@0 | 2737 | case 'or': |
michael@0 | 2738 | b = stack.pop(); |
michael@0 | 2739 | a = stack.pop(); |
michael@0 | 2740 | if (isBool(a) && isBool(b)) { |
michael@0 | 2741 | stack.push(a || b); |
michael@0 | 2742 | } else { |
michael@0 | 2743 | stack.push(a | b); |
michael@0 | 2744 | } |
michael@0 | 2745 | break; |
michael@0 | 2746 | case 'pop': |
michael@0 | 2747 | stack.pop(); |
michael@0 | 2748 | break; |
michael@0 | 2749 | case 'roll': |
michael@0 | 2750 | b = stack.pop(); |
michael@0 | 2751 | a = stack.pop(); |
michael@0 | 2752 | stack.roll(a, b); |
michael@0 | 2753 | break; |
michael@0 | 2754 | case 'round': |
michael@0 | 2755 | a = stack.pop(); |
michael@0 | 2756 | stack.push(Math.round(a)); |
michael@0 | 2757 | break; |
michael@0 | 2758 | case 'sin': |
michael@0 | 2759 | a = stack.pop(); |
michael@0 | 2760 | stack.push(Math.sin(a)); |
michael@0 | 2761 | break; |
michael@0 | 2762 | case 'sqrt': |
michael@0 | 2763 | a = stack.pop(); |
michael@0 | 2764 | stack.push(Math.sqrt(a)); |
michael@0 | 2765 | break; |
michael@0 | 2766 | case 'sub': |
michael@0 | 2767 | b = stack.pop(); |
michael@0 | 2768 | a = stack.pop(); |
michael@0 | 2769 | stack.push(a - b); |
michael@0 | 2770 | break; |
michael@0 | 2771 | case 'true': |
michael@0 | 2772 | stack.push(true); |
michael@0 | 2773 | break; |
michael@0 | 2774 | case 'truncate': |
michael@0 | 2775 | a = stack.pop(); |
michael@0 | 2776 | a = a < 0 ? Math.ceil(a) : Math.floor(a); |
michael@0 | 2777 | stack.push(a); |
michael@0 | 2778 | break; |
michael@0 | 2779 | case 'xor': |
michael@0 | 2780 | b = stack.pop(); |
michael@0 | 2781 | a = stack.pop(); |
michael@0 | 2782 | if (isBool(a) && isBool(b)) { |
michael@0 | 2783 | stack.push(a != b); |
michael@0 | 2784 | } else { |
michael@0 | 2785 | stack.push(a ^ b); |
michael@0 | 2786 | } |
michael@0 | 2787 | break; |
michael@0 | 2788 | default: |
michael@0 | 2789 | error('Unknown operator ' + operator); |
michael@0 | 2790 | break; |
michael@0 | 2791 | } |
michael@0 | 2792 | } |
michael@0 | 2793 | return stack.stack; |
michael@0 | 2794 | } |
michael@0 | 2795 | }; |
michael@0 | 2796 | return PostScriptEvaluator; |
michael@0 | 2797 | })(); |
michael@0 | 2798 | |
michael@0 | 2799 | |
michael@0 | 2800 | var HIGHLIGHT_OFFSET = 4; // px |
michael@0 | 2801 | var SUPPORTED_TYPES = ['Link', 'Text', 'Widget']; |
michael@0 | 2802 | |
michael@0 | 2803 | var Annotation = (function AnnotationClosure() { |
michael@0 | 2804 | // 12.5.5: Algorithm: Appearance streams |
michael@0 | 2805 | function getTransformMatrix(rect, bbox, matrix) { |
michael@0 | 2806 | var bounds = Util.getAxialAlignedBoundingBox(bbox, matrix); |
michael@0 | 2807 | var minX = bounds[0]; |
michael@0 | 2808 | var minY = bounds[1]; |
michael@0 | 2809 | var maxX = bounds[2]; |
michael@0 | 2810 | var maxY = bounds[3]; |
michael@0 | 2811 | |
michael@0 | 2812 | if (minX === maxX || minY === maxY) { |
michael@0 | 2813 | // From real-life file, bbox was [0, 0, 0, 0]. In this case, |
michael@0 | 2814 | // just apply the transform for rect |
michael@0 | 2815 | return [1, 0, 0, 1, rect[0], rect[1]]; |
michael@0 | 2816 | } |
michael@0 | 2817 | |
michael@0 | 2818 | var xRatio = (rect[2] - rect[0]) / (maxX - minX); |
michael@0 | 2819 | var yRatio = (rect[3] - rect[1]) / (maxY - minY); |
michael@0 | 2820 | return [ |
michael@0 | 2821 | xRatio, |
michael@0 | 2822 | 0, |
michael@0 | 2823 | 0, |
michael@0 | 2824 | yRatio, |
michael@0 | 2825 | rect[0] - minX * xRatio, |
michael@0 | 2826 | rect[1] - minY * yRatio |
michael@0 | 2827 | ]; |
michael@0 | 2828 | } |
michael@0 | 2829 | |
michael@0 | 2830 | function getDefaultAppearance(dict) { |
michael@0 | 2831 | var appearanceState = dict.get('AP'); |
michael@0 | 2832 | if (!isDict(appearanceState)) { |
michael@0 | 2833 | return; |
michael@0 | 2834 | } |
michael@0 | 2835 | |
michael@0 | 2836 | var appearance; |
michael@0 | 2837 | var appearances = appearanceState.get('N'); |
michael@0 | 2838 | if (isDict(appearances)) { |
michael@0 | 2839 | var as = dict.get('AS'); |
michael@0 | 2840 | if (as && appearances.has(as.name)) { |
michael@0 | 2841 | appearance = appearances.get(as.name); |
michael@0 | 2842 | } |
michael@0 | 2843 | } else { |
michael@0 | 2844 | appearance = appearances; |
michael@0 | 2845 | } |
michael@0 | 2846 | return appearance; |
michael@0 | 2847 | } |
michael@0 | 2848 | |
michael@0 | 2849 | function Annotation(params) { |
michael@0 | 2850 | if (params.data) { |
michael@0 | 2851 | this.data = params.data; |
michael@0 | 2852 | return; |
michael@0 | 2853 | } |
michael@0 | 2854 | |
michael@0 | 2855 | var dict = params.dict; |
michael@0 | 2856 | var data = this.data = {}; |
michael@0 | 2857 | |
michael@0 | 2858 | data.subtype = dict.get('Subtype').name; |
michael@0 | 2859 | var rect = dict.get('Rect') || [0, 0, 0, 0]; |
michael@0 | 2860 | data.rect = Util.normalizeRect(rect); |
michael@0 | 2861 | data.annotationFlags = dict.get('F'); |
michael@0 | 2862 | |
michael@0 | 2863 | var color = dict.get('C'); |
michael@0 | 2864 | if (isArray(color) && color.length === 3) { |
michael@0 | 2865 | // TODO(mack): currently only supporting rgb; need support different |
michael@0 | 2866 | // colorspaces |
michael@0 | 2867 | data.color = color; |
michael@0 | 2868 | } else { |
michael@0 | 2869 | data.color = [0, 0, 0]; |
michael@0 | 2870 | } |
michael@0 | 2871 | |
michael@0 | 2872 | // Some types of annotations have border style dict which has more |
michael@0 | 2873 | // info than the border array |
michael@0 | 2874 | if (dict.has('BS')) { |
michael@0 | 2875 | var borderStyle = dict.get('BS'); |
michael@0 | 2876 | data.borderWidth = borderStyle.has('W') ? borderStyle.get('W') : 1; |
michael@0 | 2877 | } else { |
michael@0 | 2878 | var borderArray = dict.get('Border') || [0, 0, 1]; |
michael@0 | 2879 | data.borderWidth = borderArray[2] || 0; |
michael@0 | 2880 | |
michael@0 | 2881 | // TODO: implement proper support for annotations with line dash patterns. |
michael@0 | 2882 | var dashArray = borderArray[3]; |
michael@0 | 2883 | if (data.borderWidth > 0 && dashArray && isArray(dashArray)) { |
michael@0 | 2884 | var dashArrayLength = dashArray.length; |
michael@0 | 2885 | if (dashArrayLength > 0) { |
michael@0 | 2886 | // According to the PDF specification: the elements in a dashArray |
michael@0 | 2887 | // shall be numbers that are nonnegative and not all equal to zero. |
michael@0 | 2888 | var isInvalid = false; |
michael@0 | 2889 | var numPositive = 0; |
michael@0 | 2890 | for (var i = 0; i < dashArrayLength; i++) { |
michael@0 | 2891 | var validNumber = (+dashArray[i] >= 0); |
michael@0 | 2892 | if (!validNumber) { |
michael@0 | 2893 | isInvalid = true; |
michael@0 | 2894 | break; |
michael@0 | 2895 | } else if (dashArray[i] > 0) { |
michael@0 | 2896 | numPositive++; |
michael@0 | 2897 | } |
michael@0 | 2898 | } |
michael@0 | 2899 | if (isInvalid || numPositive === 0) { |
michael@0 | 2900 | data.borderWidth = 0; |
michael@0 | 2901 | } |
michael@0 | 2902 | } |
michael@0 | 2903 | } |
michael@0 | 2904 | } |
michael@0 | 2905 | |
michael@0 | 2906 | this.appearance = getDefaultAppearance(dict); |
michael@0 | 2907 | data.hasAppearance = !!this.appearance; |
michael@0 | 2908 | data.id = params.ref.num; |
michael@0 | 2909 | } |
michael@0 | 2910 | |
michael@0 | 2911 | Annotation.prototype = { |
michael@0 | 2912 | |
michael@0 | 2913 | getData: function Annotation_getData() { |
michael@0 | 2914 | return this.data; |
michael@0 | 2915 | }, |
michael@0 | 2916 | |
michael@0 | 2917 | hasHtml: function Annotation_hasHtml() { |
michael@0 | 2918 | return false; |
michael@0 | 2919 | }, |
michael@0 | 2920 | |
michael@0 | 2921 | getHtmlElement: function Annotation_getHtmlElement(commonObjs) { |
michael@0 | 2922 | throw new NotImplementedException( |
michael@0 | 2923 | 'getHtmlElement() should be implemented in subclass'); |
michael@0 | 2924 | }, |
michael@0 | 2925 | |
michael@0 | 2926 | // TODO(mack): Remove this, it's not really that helpful. |
michael@0 | 2927 | getEmptyContainer: function Annotation_getEmptyContainer(tagName, rect, |
michael@0 | 2928 | borderWidth) { |
michael@0 | 2929 | assert(!isWorker, |
michael@0 | 2930 | 'getEmptyContainer() should be called from main thread'); |
michael@0 | 2931 | |
michael@0 | 2932 | var bWidth = borderWidth || 0; |
michael@0 | 2933 | |
michael@0 | 2934 | rect = rect || this.data.rect; |
michael@0 | 2935 | var element = document.createElement(tagName); |
michael@0 | 2936 | element.style.borderWidth = bWidth + 'px'; |
michael@0 | 2937 | var width = rect[2] - rect[0] - 2 * bWidth; |
michael@0 | 2938 | var height = rect[3] - rect[1] - 2 * bWidth; |
michael@0 | 2939 | element.style.width = width + 'px'; |
michael@0 | 2940 | element.style.height = height + 'px'; |
michael@0 | 2941 | return element; |
michael@0 | 2942 | }, |
michael@0 | 2943 | |
michael@0 | 2944 | isInvisible: function Annotation_isInvisible() { |
michael@0 | 2945 | var data = this.data; |
michael@0 | 2946 | if (data && SUPPORTED_TYPES.indexOf(data.subtype) !== -1) { |
michael@0 | 2947 | return false; |
michael@0 | 2948 | } else { |
michael@0 | 2949 | return !!(data && |
michael@0 | 2950 | data.annotationFlags && // Default: not invisible |
michael@0 | 2951 | data.annotationFlags & 0x1); // Invisible |
michael@0 | 2952 | } |
michael@0 | 2953 | }, |
michael@0 | 2954 | |
michael@0 | 2955 | isViewable: function Annotation_isViewable() { |
michael@0 | 2956 | var data = this.data; |
michael@0 | 2957 | return !!(!this.isInvisible() && |
michael@0 | 2958 | data && |
michael@0 | 2959 | (!data.annotationFlags || |
michael@0 | 2960 | !(data.annotationFlags & 0x22)) && // Hidden or NoView |
michael@0 | 2961 | data.rect); // rectangle is nessessary |
michael@0 | 2962 | }, |
michael@0 | 2963 | |
michael@0 | 2964 | isPrintable: function Annotation_isPrintable() { |
michael@0 | 2965 | var data = this.data; |
michael@0 | 2966 | return !!(!this.isInvisible() && |
michael@0 | 2967 | data && |
michael@0 | 2968 | data.annotationFlags && // Default: not printable |
michael@0 | 2969 | data.annotationFlags & 0x4 && // Print |
michael@0 | 2970 | data.rect); // rectangle is nessessary |
michael@0 | 2971 | }, |
michael@0 | 2972 | |
michael@0 | 2973 | loadResources: function(keys) { |
michael@0 | 2974 | var promise = new LegacyPromise(); |
michael@0 | 2975 | this.appearance.dict.getAsync('Resources').then(function(resources) { |
michael@0 | 2976 | if (!resources) { |
michael@0 | 2977 | promise.resolve(); |
michael@0 | 2978 | return; |
michael@0 | 2979 | } |
michael@0 | 2980 | var objectLoader = new ObjectLoader(resources.map, |
michael@0 | 2981 | keys, |
michael@0 | 2982 | resources.xref); |
michael@0 | 2983 | objectLoader.load().then(function() { |
michael@0 | 2984 | promise.resolve(resources); |
michael@0 | 2985 | }); |
michael@0 | 2986 | }.bind(this)); |
michael@0 | 2987 | |
michael@0 | 2988 | return promise; |
michael@0 | 2989 | }, |
michael@0 | 2990 | |
michael@0 | 2991 | getOperatorList: function Annotation_getOperatorList(evaluator) { |
michael@0 | 2992 | |
michael@0 | 2993 | var promise = new LegacyPromise(); |
michael@0 | 2994 | |
michael@0 | 2995 | if (!this.appearance) { |
michael@0 | 2996 | promise.resolve(new OperatorList()); |
michael@0 | 2997 | return promise; |
michael@0 | 2998 | } |
michael@0 | 2999 | |
michael@0 | 3000 | var data = this.data; |
michael@0 | 3001 | |
michael@0 | 3002 | var appearanceDict = this.appearance.dict; |
michael@0 | 3003 | var resourcesPromise = this.loadResources([ |
michael@0 | 3004 | 'ExtGState', |
michael@0 | 3005 | 'ColorSpace', |
michael@0 | 3006 | 'Pattern', |
michael@0 | 3007 | 'Shading', |
michael@0 | 3008 | 'XObject', |
michael@0 | 3009 | 'Font' |
michael@0 | 3010 | // ProcSet |
michael@0 | 3011 | // Properties |
michael@0 | 3012 | ]); |
michael@0 | 3013 | var bbox = appearanceDict.get('BBox') || [0, 0, 1, 1]; |
michael@0 | 3014 | var matrix = appearanceDict.get('Matrix') || [1, 0, 0, 1, 0 ,0]; |
michael@0 | 3015 | var transform = getTransformMatrix(data.rect, bbox, matrix); |
michael@0 | 3016 | |
michael@0 | 3017 | resourcesPromise.then(function(resources) { |
michael@0 | 3018 | var opList = new OperatorList(); |
michael@0 | 3019 | opList.addOp(OPS.beginAnnotation, [data.rect, transform, matrix]); |
michael@0 | 3020 | evaluator.getOperatorList(this.appearance, resources, opList); |
michael@0 | 3021 | opList.addOp(OPS.endAnnotation, []); |
michael@0 | 3022 | promise.resolve(opList); |
michael@0 | 3023 | |
michael@0 | 3024 | this.appearance.reset(); |
michael@0 | 3025 | }.bind(this)); |
michael@0 | 3026 | |
michael@0 | 3027 | return promise; |
michael@0 | 3028 | } |
michael@0 | 3029 | }; |
michael@0 | 3030 | |
michael@0 | 3031 | Annotation.getConstructor = |
michael@0 | 3032 | function Annotation_getConstructor(subtype, fieldType) { |
michael@0 | 3033 | |
michael@0 | 3034 | if (!subtype) { |
michael@0 | 3035 | return; |
michael@0 | 3036 | } |
michael@0 | 3037 | |
michael@0 | 3038 | // TODO(mack): Implement FreeText annotations |
michael@0 | 3039 | if (subtype === 'Link') { |
michael@0 | 3040 | return LinkAnnotation; |
michael@0 | 3041 | } else if (subtype === 'Text') { |
michael@0 | 3042 | return TextAnnotation; |
michael@0 | 3043 | } else if (subtype === 'Widget') { |
michael@0 | 3044 | if (!fieldType) { |
michael@0 | 3045 | return; |
michael@0 | 3046 | } |
michael@0 | 3047 | |
michael@0 | 3048 | if (fieldType === 'Tx') { |
michael@0 | 3049 | return TextWidgetAnnotation; |
michael@0 | 3050 | } else { |
michael@0 | 3051 | return WidgetAnnotation; |
michael@0 | 3052 | } |
michael@0 | 3053 | } else { |
michael@0 | 3054 | return Annotation; |
michael@0 | 3055 | } |
michael@0 | 3056 | }; |
michael@0 | 3057 | |
michael@0 | 3058 | // TODO(mack): Support loading annotation from data |
michael@0 | 3059 | Annotation.fromData = function Annotation_fromData(data) { |
michael@0 | 3060 | var subtype = data.subtype; |
michael@0 | 3061 | var fieldType = data.fieldType; |
michael@0 | 3062 | var Constructor = Annotation.getConstructor(subtype, fieldType); |
michael@0 | 3063 | if (Constructor) { |
michael@0 | 3064 | return new Constructor({ data: data }); |
michael@0 | 3065 | } |
michael@0 | 3066 | }; |
michael@0 | 3067 | |
michael@0 | 3068 | Annotation.fromRef = function Annotation_fromRef(xref, ref) { |
michael@0 | 3069 | |
michael@0 | 3070 | var dict = xref.fetchIfRef(ref); |
michael@0 | 3071 | if (!isDict(dict)) { |
michael@0 | 3072 | return; |
michael@0 | 3073 | } |
michael@0 | 3074 | |
michael@0 | 3075 | var subtype = dict.get('Subtype'); |
michael@0 | 3076 | subtype = isName(subtype) ? subtype.name : ''; |
michael@0 | 3077 | if (!subtype) { |
michael@0 | 3078 | return; |
michael@0 | 3079 | } |
michael@0 | 3080 | |
michael@0 | 3081 | var fieldType = Util.getInheritableProperty(dict, 'FT'); |
michael@0 | 3082 | fieldType = isName(fieldType) ? fieldType.name : ''; |
michael@0 | 3083 | |
michael@0 | 3084 | var Constructor = Annotation.getConstructor(subtype, fieldType); |
michael@0 | 3085 | if (!Constructor) { |
michael@0 | 3086 | return; |
michael@0 | 3087 | } |
michael@0 | 3088 | |
michael@0 | 3089 | var params = { |
michael@0 | 3090 | dict: dict, |
michael@0 | 3091 | ref: ref, |
michael@0 | 3092 | }; |
michael@0 | 3093 | |
michael@0 | 3094 | var annotation = new Constructor(params); |
michael@0 | 3095 | |
michael@0 | 3096 | if (annotation.isViewable() || annotation.isPrintable()) { |
michael@0 | 3097 | return annotation; |
michael@0 | 3098 | } else { |
michael@0 | 3099 | warn('unimplemented annotation type: ' + subtype); |
michael@0 | 3100 | } |
michael@0 | 3101 | }; |
michael@0 | 3102 | |
michael@0 | 3103 | Annotation.appendToOperatorList = function Annotation_appendToOperatorList( |
michael@0 | 3104 | annotations, opList, pdfManager, partialEvaluator, intent) { |
michael@0 | 3105 | |
michael@0 | 3106 | function reject(e) { |
michael@0 | 3107 | annotationsReadyPromise.reject(e); |
michael@0 | 3108 | } |
michael@0 | 3109 | |
michael@0 | 3110 | var annotationsReadyPromise = new LegacyPromise(); |
michael@0 | 3111 | |
michael@0 | 3112 | var annotationPromises = []; |
michael@0 | 3113 | for (var i = 0, n = annotations.length; i < n; ++i) { |
michael@0 | 3114 | if (intent === 'display' && annotations[i].isViewable() || |
michael@0 | 3115 | intent === 'print' && annotations[i].isPrintable()) { |
michael@0 | 3116 | annotationPromises.push( |
michael@0 | 3117 | annotations[i].getOperatorList(partialEvaluator)); |
michael@0 | 3118 | } |
michael@0 | 3119 | } |
michael@0 | 3120 | Promise.all(annotationPromises).then(function(datas) { |
michael@0 | 3121 | opList.addOp(OPS.beginAnnotations, []); |
michael@0 | 3122 | for (var i = 0, n = datas.length; i < n; ++i) { |
michael@0 | 3123 | var annotOpList = datas[i]; |
michael@0 | 3124 | opList.addOpList(annotOpList); |
michael@0 | 3125 | } |
michael@0 | 3126 | opList.addOp(OPS.endAnnotations, []); |
michael@0 | 3127 | annotationsReadyPromise.resolve(); |
michael@0 | 3128 | }, reject); |
michael@0 | 3129 | |
michael@0 | 3130 | return annotationsReadyPromise; |
michael@0 | 3131 | }; |
michael@0 | 3132 | |
michael@0 | 3133 | return Annotation; |
michael@0 | 3134 | })(); |
michael@0 | 3135 | PDFJS.Annotation = Annotation; |
michael@0 | 3136 | |
michael@0 | 3137 | |
michael@0 | 3138 | var WidgetAnnotation = (function WidgetAnnotationClosure() { |
michael@0 | 3139 | |
michael@0 | 3140 | function WidgetAnnotation(params) { |
michael@0 | 3141 | Annotation.call(this, params); |
michael@0 | 3142 | |
michael@0 | 3143 | if (params.data) { |
michael@0 | 3144 | return; |
michael@0 | 3145 | } |
michael@0 | 3146 | |
michael@0 | 3147 | var dict = params.dict; |
michael@0 | 3148 | var data = this.data; |
michael@0 | 3149 | |
michael@0 | 3150 | data.fieldValue = stringToPDFString( |
michael@0 | 3151 | Util.getInheritableProperty(dict, 'V') || ''); |
michael@0 | 3152 | data.alternativeText = stringToPDFString(dict.get('TU') || ''); |
michael@0 | 3153 | data.defaultAppearance = Util.getInheritableProperty(dict, 'DA') || ''; |
michael@0 | 3154 | var fieldType = Util.getInheritableProperty(dict, 'FT'); |
michael@0 | 3155 | data.fieldType = isName(fieldType) ? fieldType.name : ''; |
michael@0 | 3156 | data.fieldFlags = Util.getInheritableProperty(dict, 'Ff') || 0; |
michael@0 | 3157 | this.fieldResources = Util.getInheritableProperty(dict, 'DR') || Dict.empty; |
michael@0 | 3158 | |
michael@0 | 3159 | // Building the full field name by collecting the field and |
michael@0 | 3160 | // its ancestors 'T' data and joining them using '.'. |
michael@0 | 3161 | var fieldName = []; |
michael@0 | 3162 | var namedItem = dict; |
michael@0 | 3163 | var ref = params.ref; |
michael@0 | 3164 | while (namedItem) { |
michael@0 | 3165 | var parent = namedItem.get('Parent'); |
michael@0 | 3166 | var parentRef = namedItem.getRaw('Parent'); |
michael@0 | 3167 | var name = namedItem.get('T'); |
michael@0 | 3168 | if (name) { |
michael@0 | 3169 | fieldName.unshift(stringToPDFString(name)); |
michael@0 | 3170 | } else { |
michael@0 | 3171 | // The field name is absent, that means more than one field |
michael@0 | 3172 | // with the same name may exist. Replacing the empty name |
michael@0 | 3173 | // with the '`' plus index in the parent's 'Kids' array. |
michael@0 | 3174 | // This is not in the PDF spec but necessary to id the |
michael@0 | 3175 | // the input controls. |
michael@0 | 3176 | var kids = parent.get('Kids'); |
michael@0 | 3177 | var j, jj; |
michael@0 | 3178 | for (j = 0, jj = kids.length; j < jj; j++) { |
michael@0 | 3179 | var kidRef = kids[j]; |
michael@0 | 3180 | if (kidRef.num == ref.num && kidRef.gen == ref.gen) { |
michael@0 | 3181 | break; |
michael@0 | 3182 | } |
michael@0 | 3183 | } |
michael@0 | 3184 | fieldName.unshift('`' + j); |
michael@0 | 3185 | } |
michael@0 | 3186 | namedItem = parent; |
michael@0 | 3187 | ref = parentRef; |
michael@0 | 3188 | } |
michael@0 | 3189 | data.fullName = fieldName.join('.'); |
michael@0 | 3190 | } |
michael@0 | 3191 | |
michael@0 | 3192 | var parent = Annotation.prototype; |
michael@0 | 3193 | Util.inherit(WidgetAnnotation, Annotation, { |
michael@0 | 3194 | isViewable: function WidgetAnnotation_isViewable() { |
michael@0 | 3195 | if (this.data.fieldType === 'Sig') { |
michael@0 | 3196 | warn('unimplemented annotation type: Widget signature'); |
michael@0 | 3197 | return false; |
michael@0 | 3198 | } |
michael@0 | 3199 | |
michael@0 | 3200 | return parent.isViewable.call(this); |
michael@0 | 3201 | } |
michael@0 | 3202 | }); |
michael@0 | 3203 | |
michael@0 | 3204 | return WidgetAnnotation; |
michael@0 | 3205 | })(); |
michael@0 | 3206 | |
michael@0 | 3207 | var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() { |
michael@0 | 3208 | function TextWidgetAnnotation(params) { |
michael@0 | 3209 | WidgetAnnotation.call(this, params); |
michael@0 | 3210 | |
michael@0 | 3211 | if (params.data) { |
michael@0 | 3212 | return; |
michael@0 | 3213 | } |
michael@0 | 3214 | |
michael@0 | 3215 | this.data.textAlignment = Util.getInheritableProperty(params.dict, 'Q'); |
michael@0 | 3216 | } |
michael@0 | 3217 | |
michael@0 | 3218 | // TODO(mack): This dupes some of the logic in CanvasGraphics.setFont() |
michael@0 | 3219 | function setTextStyles(element, item, fontObj) { |
michael@0 | 3220 | |
michael@0 | 3221 | var style = element.style; |
michael@0 | 3222 | style.fontSize = item.fontSize + 'px'; |
michael@0 | 3223 | style.direction = item.fontDirection < 0 ? 'rtl': 'ltr'; |
michael@0 | 3224 | |
michael@0 | 3225 | if (!fontObj) { |
michael@0 | 3226 | return; |
michael@0 | 3227 | } |
michael@0 | 3228 | |
michael@0 | 3229 | style.fontWeight = fontObj.black ? |
michael@0 | 3230 | (fontObj.bold ? 'bolder' : 'bold') : |
michael@0 | 3231 | (fontObj.bold ? 'bold' : 'normal'); |
michael@0 | 3232 | style.fontStyle = fontObj.italic ? 'italic' : 'normal'; |
michael@0 | 3233 | |
michael@0 | 3234 | var fontName = fontObj.loadedName; |
michael@0 | 3235 | var fontFamily = fontName ? '"' + fontName + '", ' : ''; |
michael@0 | 3236 | // Use a reasonable default font if the font doesn't specify a fallback |
michael@0 | 3237 | var fallbackName = fontObj.fallbackName || 'Helvetica, sans-serif'; |
michael@0 | 3238 | style.fontFamily = fontFamily + fallbackName; |
michael@0 | 3239 | } |
michael@0 | 3240 | |
michael@0 | 3241 | |
michael@0 | 3242 | Util.inherit(TextWidgetAnnotation, WidgetAnnotation, { |
michael@0 | 3243 | hasHtml: function TextWidgetAnnotation_hasHtml() { |
michael@0 | 3244 | return !this.data.hasAppearance && !!this.data.fieldValue; |
michael@0 | 3245 | }, |
michael@0 | 3246 | |
michael@0 | 3247 | getHtmlElement: function TextWidgetAnnotation_getHtmlElement(commonObjs) { |
michael@0 | 3248 | assert(!isWorker, 'getHtmlElement() shall be called from main thread'); |
michael@0 | 3249 | |
michael@0 | 3250 | var item = this.data; |
michael@0 | 3251 | |
michael@0 | 3252 | var element = this.getEmptyContainer('div'); |
michael@0 | 3253 | element.style.display = 'table'; |
michael@0 | 3254 | |
michael@0 | 3255 | var content = document.createElement('div'); |
michael@0 | 3256 | content.textContent = item.fieldValue; |
michael@0 | 3257 | var textAlignment = item.textAlignment; |
michael@0 | 3258 | content.style.textAlign = ['left', 'center', 'right'][textAlignment]; |
michael@0 | 3259 | content.style.verticalAlign = 'middle'; |
michael@0 | 3260 | content.style.display = 'table-cell'; |
michael@0 | 3261 | |
michael@0 | 3262 | var fontObj = item.fontRefName ? |
michael@0 | 3263 | commonObjs.getData(item.fontRefName) : null; |
michael@0 | 3264 | setTextStyles(content, item, fontObj); |
michael@0 | 3265 | |
michael@0 | 3266 | element.appendChild(content); |
michael@0 | 3267 | |
michael@0 | 3268 | return element; |
michael@0 | 3269 | }, |
michael@0 | 3270 | |
michael@0 | 3271 | getOperatorList: function TextWidgetAnnotation_getOperatorList(evaluator) { |
michael@0 | 3272 | if (this.appearance) { |
michael@0 | 3273 | return Annotation.prototype.getOperatorList.call(this, evaluator); |
michael@0 | 3274 | } |
michael@0 | 3275 | |
michael@0 | 3276 | var promise = new LegacyPromise(); |
michael@0 | 3277 | var opList = new OperatorList(); |
michael@0 | 3278 | var data = this.data; |
michael@0 | 3279 | |
michael@0 | 3280 | // Even if there is an appearance stream, ignore it. This is the |
michael@0 | 3281 | // behaviour used by Adobe Reader. |
michael@0 | 3282 | |
michael@0 | 3283 | var defaultAppearance = data.defaultAppearance; |
michael@0 | 3284 | if (!defaultAppearance) { |
michael@0 | 3285 | promise.resolve(opList); |
michael@0 | 3286 | return promise; |
michael@0 | 3287 | } |
michael@0 | 3288 | |
michael@0 | 3289 | // Include any font resources found in the default appearance |
michael@0 | 3290 | |
michael@0 | 3291 | var stream = new Stream(stringToBytes(defaultAppearance)); |
michael@0 | 3292 | evaluator.getOperatorList(stream, this.fieldResources, opList); |
michael@0 | 3293 | var appearanceFnArray = opList.fnArray; |
michael@0 | 3294 | var appearanceArgsArray = opList.argsArray; |
michael@0 | 3295 | var fnArray = []; |
michael@0 | 3296 | |
michael@0 | 3297 | // TODO(mack): Add support for stroke color |
michael@0 | 3298 | data.rgb = [0, 0, 0]; |
michael@0 | 3299 | // TODO THIS DOESN'T MAKE ANY SENSE SINCE THE fnArray IS EMPTY! |
michael@0 | 3300 | for (var i = 0, n = fnArray.length; i < n; ++i) { |
michael@0 | 3301 | var fnId = appearanceFnArray[i]; |
michael@0 | 3302 | var args = appearanceArgsArray[i]; |
michael@0 | 3303 | |
michael@0 | 3304 | if (fnId === OPS.setFont) { |
michael@0 | 3305 | data.fontRefName = args[0]; |
michael@0 | 3306 | var size = args[1]; |
michael@0 | 3307 | if (size < 0) { |
michael@0 | 3308 | data.fontDirection = -1; |
michael@0 | 3309 | data.fontSize = -size; |
michael@0 | 3310 | } else { |
michael@0 | 3311 | data.fontDirection = 1; |
michael@0 | 3312 | data.fontSize = size; |
michael@0 | 3313 | } |
michael@0 | 3314 | } else if (fnId === OPS.setFillRGBColor) { |
michael@0 | 3315 | data.rgb = args; |
michael@0 | 3316 | } else if (fnId === OPS.setFillGray) { |
michael@0 | 3317 | var rgbValue = args[0] * 255; |
michael@0 | 3318 | data.rgb = [rgbValue, rgbValue, rgbValue]; |
michael@0 | 3319 | } |
michael@0 | 3320 | } |
michael@0 | 3321 | promise.resolve(opList); |
michael@0 | 3322 | return promise; |
michael@0 | 3323 | } |
michael@0 | 3324 | }); |
michael@0 | 3325 | |
michael@0 | 3326 | return TextWidgetAnnotation; |
michael@0 | 3327 | })(); |
michael@0 | 3328 | |
michael@0 | 3329 | var InteractiveAnnotation = (function InteractiveAnnotationClosure() { |
michael@0 | 3330 | function InteractiveAnnotation(params) { |
michael@0 | 3331 | Annotation.call(this, params); |
michael@0 | 3332 | } |
michael@0 | 3333 | |
michael@0 | 3334 | Util.inherit(InteractiveAnnotation, Annotation, { |
michael@0 | 3335 | hasHtml: function InteractiveAnnotation_hasHtml() { |
michael@0 | 3336 | return true; |
michael@0 | 3337 | }, |
michael@0 | 3338 | |
michael@0 | 3339 | highlight: function InteractiveAnnotation_highlight() { |
michael@0 | 3340 | if (this.highlightElement && |
michael@0 | 3341 | this.highlightElement.hasAttribute('hidden')) { |
michael@0 | 3342 | this.highlightElement.removeAttribute('hidden'); |
michael@0 | 3343 | } |
michael@0 | 3344 | }, |
michael@0 | 3345 | |
michael@0 | 3346 | unhighlight: function InteractiveAnnotation_unhighlight() { |
michael@0 | 3347 | if (this.highlightElement && |
michael@0 | 3348 | !this.highlightElement.hasAttribute('hidden')) { |
michael@0 | 3349 | this.highlightElement.setAttribute('hidden', true); |
michael@0 | 3350 | } |
michael@0 | 3351 | }, |
michael@0 | 3352 | |
michael@0 | 3353 | initContainer: function InteractiveAnnotation_initContainer() { |
michael@0 | 3354 | |
michael@0 | 3355 | var item = this.data; |
michael@0 | 3356 | var rect = item.rect; |
michael@0 | 3357 | |
michael@0 | 3358 | var container = this.getEmptyContainer('section', rect, item.borderWidth); |
michael@0 | 3359 | container.style.backgroundColor = item.color; |
michael@0 | 3360 | |
michael@0 | 3361 | var color = item.color; |
michael@0 | 3362 | var rgb = []; |
michael@0 | 3363 | for (var i = 0; i < 3; ++i) { |
michael@0 | 3364 | rgb[i] = Math.round(color[i] * 255); |
michael@0 | 3365 | } |
michael@0 | 3366 | item.colorCssRgb = Util.makeCssRgb(rgb); |
michael@0 | 3367 | |
michael@0 | 3368 | var highlight = document.createElement('div'); |
michael@0 | 3369 | highlight.className = 'annotationHighlight'; |
michael@0 | 3370 | highlight.style.left = highlight.style.top = -HIGHLIGHT_OFFSET + 'px'; |
michael@0 | 3371 | highlight.style.right = highlight.style.bottom = -HIGHLIGHT_OFFSET + 'px'; |
michael@0 | 3372 | highlight.setAttribute('hidden', true); |
michael@0 | 3373 | |
michael@0 | 3374 | this.highlightElement = highlight; |
michael@0 | 3375 | container.appendChild(this.highlightElement); |
michael@0 | 3376 | |
michael@0 | 3377 | return container; |
michael@0 | 3378 | } |
michael@0 | 3379 | }); |
michael@0 | 3380 | |
michael@0 | 3381 | return InteractiveAnnotation; |
michael@0 | 3382 | })(); |
michael@0 | 3383 | |
michael@0 | 3384 | var TextAnnotation = (function TextAnnotationClosure() { |
michael@0 | 3385 | function TextAnnotation(params) { |
michael@0 | 3386 | InteractiveAnnotation.call(this, params); |
michael@0 | 3387 | |
michael@0 | 3388 | if (params.data) { |
michael@0 | 3389 | return; |
michael@0 | 3390 | } |
michael@0 | 3391 | |
michael@0 | 3392 | var dict = params.dict; |
michael@0 | 3393 | var data = this.data; |
michael@0 | 3394 | |
michael@0 | 3395 | var content = dict.get('Contents'); |
michael@0 | 3396 | var title = dict.get('T'); |
michael@0 | 3397 | data.content = stringToPDFString(content || ''); |
michael@0 | 3398 | data.title = stringToPDFString(title || ''); |
michael@0 | 3399 | |
michael@0 | 3400 | if (data.hasAppearance) { |
michael@0 | 3401 | data.name = 'NoIcon'; |
michael@0 | 3402 | } else { |
michael@0 | 3403 | data.name = dict.has('Name') ? dict.get('Name').name : 'Note'; |
michael@0 | 3404 | } |
michael@0 | 3405 | |
michael@0 | 3406 | if (dict.has('C')) { |
michael@0 | 3407 | data.hasBgColor = true; |
michael@0 | 3408 | } |
michael@0 | 3409 | } |
michael@0 | 3410 | |
michael@0 | 3411 | var ANNOT_MIN_SIZE = 10; |
michael@0 | 3412 | |
michael@0 | 3413 | Util.inherit(TextAnnotation, InteractiveAnnotation, { |
michael@0 | 3414 | |
michael@0 | 3415 | getHtmlElement: function TextAnnotation_getHtmlElement(commonObjs) { |
michael@0 | 3416 | assert(!isWorker, 'getHtmlElement() shall be called from main thread'); |
michael@0 | 3417 | |
michael@0 | 3418 | var item = this.data; |
michael@0 | 3419 | var rect = item.rect; |
michael@0 | 3420 | |
michael@0 | 3421 | // sanity check because of OOo-generated PDFs |
michael@0 | 3422 | if ((rect[3] - rect[1]) < ANNOT_MIN_SIZE) { |
michael@0 | 3423 | rect[3] = rect[1] + ANNOT_MIN_SIZE; |
michael@0 | 3424 | } |
michael@0 | 3425 | if ((rect[2] - rect[0]) < ANNOT_MIN_SIZE) { |
michael@0 | 3426 | rect[2] = rect[0] + (rect[3] - rect[1]); // make it square |
michael@0 | 3427 | } |
michael@0 | 3428 | |
michael@0 | 3429 | var container = this.initContainer(); |
michael@0 | 3430 | container.className = 'annotText'; |
michael@0 | 3431 | |
michael@0 | 3432 | var image = document.createElement('img'); |
michael@0 | 3433 | image.style.height = container.style.height; |
michael@0 | 3434 | image.style.width = container.style.width; |
michael@0 | 3435 | var iconName = item.name; |
michael@0 | 3436 | image.src = PDFJS.imageResourcesPath + 'annotation-' + |
michael@0 | 3437 | iconName.toLowerCase() + '.svg'; |
michael@0 | 3438 | image.alt = '[{{type}} Annotation]'; |
michael@0 | 3439 | image.dataset.l10nId = 'text_annotation_type'; |
michael@0 | 3440 | image.dataset.l10nArgs = JSON.stringify({type: iconName}); |
michael@0 | 3441 | |
michael@0 | 3442 | var contentWrapper = document.createElement('div'); |
michael@0 | 3443 | contentWrapper.className = 'annotTextContentWrapper'; |
michael@0 | 3444 | contentWrapper.style.left = Math.floor(rect[2] - rect[0] + 5) + 'px'; |
michael@0 | 3445 | contentWrapper.style.top = '-10px'; |
michael@0 | 3446 | |
michael@0 | 3447 | var content = document.createElement('div'); |
michael@0 | 3448 | content.className = 'annotTextContent'; |
michael@0 | 3449 | content.setAttribute('hidden', true); |
michael@0 | 3450 | |
michael@0 | 3451 | var i, ii; |
michael@0 | 3452 | if (item.hasBgColor) { |
michael@0 | 3453 | var color = item.color; |
michael@0 | 3454 | var rgb = []; |
michael@0 | 3455 | for (i = 0; i < 3; ++i) { |
michael@0 | 3456 | // Enlighten the color (70%) |
michael@0 | 3457 | var c = Math.round(color[i] * 255); |
michael@0 | 3458 | rgb[i] = Math.round((255 - c) * 0.7) + c; |
michael@0 | 3459 | } |
michael@0 | 3460 | content.style.backgroundColor = Util.makeCssRgb(rgb); |
michael@0 | 3461 | } |
michael@0 | 3462 | |
michael@0 | 3463 | var title = document.createElement('h1'); |
michael@0 | 3464 | var text = document.createElement('p'); |
michael@0 | 3465 | title.textContent = item.title; |
michael@0 | 3466 | |
michael@0 | 3467 | if (!item.content && !item.title) { |
michael@0 | 3468 | content.setAttribute('hidden', true); |
michael@0 | 3469 | } else { |
michael@0 | 3470 | var e = document.createElement('span'); |
michael@0 | 3471 | var lines = item.content.split(/(?:\r\n?|\n)/); |
michael@0 | 3472 | for (i = 0, ii = lines.length; i < ii; ++i) { |
michael@0 | 3473 | var line = lines[i]; |
michael@0 | 3474 | e.appendChild(document.createTextNode(line)); |
michael@0 | 3475 | if (i < (ii - 1)) { |
michael@0 | 3476 | e.appendChild(document.createElement('br')); |
michael@0 | 3477 | } |
michael@0 | 3478 | } |
michael@0 | 3479 | text.appendChild(e); |
michael@0 | 3480 | |
michael@0 | 3481 | var pinned = false; |
michael@0 | 3482 | |
michael@0 | 3483 | var showAnnotation = function showAnnotation(pin) { |
michael@0 | 3484 | if (pin) { |
michael@0 | 3485 | pinned = true; |
michael@0 | 3486 | } |
michael@0 | 3487 | if (content.hasAttribute('hidden')) { |
michael@0 | 3488 | container.style.zIndex += 1; |
michael@0 | 3489 | content.removeAttribute('hidden'); |
michael@0 | 3490 | } |
michael@0 | 3491 | }; |
michael@0 | 3492 | |
michael@0 | 3493 | var hideAnnotation = function hideAnnotation(unpin) { |
michael@0 | 3494 | if (unpin) { |
michael@0 | 3495 | pinned = false; |
michael@0 | 3496 | } |
michael@0 | 3497 | if (!content.hasAttribute('hidden') && !pinned) { |
michael@0 | 3498 | container.style.zIndex -= 1; |
michael@0 | 3499 | content.setAttribute('hidden', true); |
michael@0 | 3500 | } |
michael@0 | 3501 | }; |
michael@0 | 3502 | |
michael@0 | 3503 | var toggleAnnotation = function toggleAnnotation() { |
michael@0 | 3504 | if (pinned) { |
michael@0 | 3505 | hideAnnotation(true); |
michael@0 | 3506 | } else { |
michael@0 | 3507 | showAnnotation(true); |
michael@0 | 3508 | } |
michael@0 | 3509 | }; |
michael@0 | 3510 | |
michael@0 | 3511 | image.addEventListener('click', function image_clickHandler() { |
michael@0 | 3512 | toggleAnnotation(); |
michael@0 | 3513 | }, false); |
michael@0 | 3514 | image.addEventListener('mouseover', function image_mouseOverHandler() { |
michael@0 | 3515 | showAnnotation(); |
michael@0 | 3516 | }, false); |
michael@0 | 3517 | image.addEventListener('mouseout', function image_mouseOutHandler() { |
michael@0 | 3518 | hideAnnotation(); |
michael@0 | 3519 | }, false); |
michael@0 | 3520 | |
michael@0 | 3521 | content.addEventListener('click', function content_clickHandler() { |
michael@0 | 3522 | hideAnnotation(true); |
michael@0 | 3523 | }, false); |
michael@0 | 3524 | } |
michael@0 | 3525 | |
michael@0 | 3526 | content.appendChild(title); |
michael@0 | 3527 | content.appendChild(text); |
michael@0 | 3528 | contentWrapper.appendChild(content); |
michael@0 | 3529 | container.appendChild(image); |
michael@0 | 3530 | container.appendChild(contentWrapper); |
michael@0 | 3531 | |
michael@0 | 3532 | return container; |
michael@0 | 3533 | } |
michael@0 | 3534 | }); |
michael@0 | 3535 | |
michael@0 | 3536 | return TextAnnotation; |
michael@0 | 3537 | })(); |
michael@0 | 3538 | |
michael@0 | 3539 | var LinkAnnotation = (function LinkAnnotationClosure() { |
michael@0 | 3540 | function LinkAnnotation(params) { |
michael@0 | 3541 | InteractiveAnnotation.call(this, params); |
michael@0 | 3542 | |
michael@0 | 3543 | if (params.data) { |
michael@0 | 3544 | return; |
michael@0 | 3545 | } |
michael@0 | 3546 | |
michael@0 | 3547 | var dict = params.dict; |
michael@0 | 3548 | var data = this.data; |
michael@0 | 3549 | |
michael@0 | 3550 | var action = dict.get('A'); |
michael@0 | 3551 | if (action) { |
michael@0 | 3552 | var linkType = action.get('S').name; |
michael@0 | 3553 | if (linkType === 'URI') { |
michael@0 | 3554 | var url = action.get('URI'); |
michael@0 | 3555 | if (isName(url)) { |
michael@0 | 3556 | // Some bad PDFs do not put parentheses around relative URLs. |
michael@0 | 3557 | url = '/' + url.name; |
michael@0 | 3558 | } else if (url) { |
michael@0 | 3559 | url = addDefaultProtocolToUrl(url); |
michael@0 | 3560 | } |
michael@0 | 3561 | // TODO: pdf spec mentions urls can be relative to a Base |
michael@0 | 3562 | // entry in the dictionary. |
michael@0 | 3563 | if (!isValidUrl(url, false)) { |
michael@0 | 3564 | url = ''; |
michael@0 | 3565 | } |
michael@0 | 3566 | data.url = url; |
michael@0 | 3567 | } else if (linkType === 'GoTo') { |
michael@0 | 3568 | data.dest = action.get('D'); |
michael@0 | 3569 | } else if (linkType === 'GoToR') { |
michael@0 | 3570 | var urlDict = action.get('F'); |
michael@0 | 3571 | if (isDict(urlDict)) { |
michael@0 | 3572 | // We assume that the 'url' is a Filspec dictionary |
michael@0 | 3573 | // and fetch the url without checking any further |
michael@0 | 3574 | url = urlDict.get('F') || ''; |
michael@0 | 3575 | } |
michael@0 | 3576 | |
michael@0 | 3577 | // TODO: pdf reference says that GoToR |
michael@0 | 3578 | // can also have 'NewWindow' attribute |
michael@0 | 3579 | if (!isValidUrl(url, false)) { |
michael@0 | 3580 | url = ''; |
michael@0 | 3581 | } |
michael@0 | 3582 | data.url = url; |
michael@0 | 3583 | data.dest = action.get('D'); |
michael@0 | 3584 | } else if (linkType === 'Named') { |
michael@0 | 3585 | data.action = action.get('N').name; |
michael@0 | 3586 | } else { |
michael@0 | 3587 | warn('unrecognized link type: ' + linkType); |
michael@0 | 3588 | } |
michael@0 | 3589 | } else if (dict.has('Dest')) { |
michael@0 | 3590 | // simple destination link |
michael@0 | 3591 | var dest = dict.get('Dest'); |
michael@0 | 3592 | data.dest = isName(dest) ? dest.name : dest; |
michael@0 | 3593 | } |
michael@0 | 3594 | } |
michael@0 | 3595 | |
michael@0 | 3596 | // Lets URLs beginning with 'www.' default to using the 'http://' protocol. |
michael@0 | 3597 | function addDefaultProtocolToUrl(url) { |
michael@0 | 3598 | if (url && url.indexOf('www.') === 0) { |
michael@0 | 3599 | return ('http://' + url); |
michael@0 | 3600 | } |
michael@0 | 3601 | return url; |
michael@0 | 3602 | } |
michael@0 | 3603 | |
michael@0 | 3604 | Util.inherit(LinkAnnotation, InteractiveAnnotation, { |
michael@0 | 3605 | hasOperatorList: function LinkAnnotation_hasOperatorList() { |
michael@0 | 3606 | return false; |
michael@0 | 3607 | }, |
michael@0 | 3608 | |
michael@0 | 3609 | getHtmlElement: function LinkAnnotation_getHtmlElement(commonObjs) { |
michael@0 | 3610 | |
michael@0 | 3611 | var container = this.initContainer(); |
michael@0 | 3612 | container.className = 'annotLink'; |
michael@0 | 3613 | |
michael@0 | 3614 | var item = this.data; |
michael@0 | 3615 | |
michael@0 | 3616 | container.style.borderColor = item.colorCssRgb; |
michael@0 | 3617 | container.style.borderStyle = 'solid'; |
michael@0 | 3618 | |
michael@0 | 3619 | var link = document.createElement('a'); |
michael@0 | 3620 | link.href = link.title = this.data.url || ''; |
michael@0 | 3621 | |
michael@0 | 3622 | container.appendChild(link); |
michael@0 | 3623 | |
michael@0 | 3624 | return container; |
michael@0 | 3625 | } |
michael@0 | 3626 | }); |
michael@0 | 3627 | |
michael@0 | 3628 | return LinkAnnotation; |
michael@0 | 3629 | })(); |
michael@0 | 3630 | |
michael@0 | 3631 | |
michael@0 | 3632 | /** |
michael@0 | 3633 | * The maximum allowed image size in total pixels e.g. width * height. Images |
michael@0 | 3634 | * above this value will not be drawn. Use -1 for no limit. |
michael@0 | 3635 | * @var {number} |
michael@0 | 3636 | */ |
michael@0 | 3637 | PDFJS.maxImageSize = (PDFJS.maxImageSize === undefined ? |
michael@0 | 3638 | -1 : PDFJS.maxImageSize); |
michael@0 | 3639 | |
michael@0 | 3640 | /** |
michael@0 | 3641 | * The url of where the predefined Adobe CMaps are located. Include trailing |
michael@0 | 3642 | * slash. |
michael@0 | 3643 | * @var {string} |
michael@0 | 3644 | */ |
michael@0 | 3645 | PDFJS.cMapUrl = (PDFJS.cMapUrl === undefined ? null : PDFJS.cMapUrl); |
michael@0 | 3646 | |
michael@0 | 3647 | /** |
michael@0 | 3648 | * Specifies if CMaps are binary packed. |
michael@0 | 3649 | * @var {boolean} |
michael@0 | 3650 | */ |
michael@0 | 3651 | PDFJS.cMapPacked = PDFJS.cMapPacked === undefined ? false : PDFJS.cMapPacked; |
michael@0 | 3652 | |
michael@0 | 3653 | /* |
michael@0 | 3654 | * By default fonts are converted to OpenType fonts and loaded via font face |
michael@0 | 3655 | * rules. If disabled, the font will be rendered using a built in font renderer |
michael@0 | 3656 | * that constructs the glyphs with primitive path commands. |
michael@0 | 3657 | * @var {boolean} |
michael@0 | 3658 | */ |
michael@0 | 3659 | PDFJS.disableFontFace = (PDFJS.disableFontFace === undefined ? |
michael@0 | 3660 | false : PDFJS.disableFontFace); |
michael@0 | 3661 | |
michael@0 | 3662 | /** |
michael@0 | 3663 | * Path for image resources, mainly for annotation icons. Include trailing |
michael@0 | 3664 | * slash. |
michael@0 | 3665 | * @var {string} |
michael@0 | 3666 | */ |
michael@0 | 3667 | PDFJS.imageResourcesPath = (PDFJS.imageResourcesPath === undefined ? |
michael@0 | 3668 | '' : PDFJS.imageResourcesPath); |
michael@0 | 3669 | |
michael@0 | 3670 | /** |
michael@0 | 3671 | * Disable the web worker and run all code on the main thread. This will happen |
michael@0 | 3672 | * automatically if the browser doesn't support workers or sending typed arrays |
michael@0 | 3673 | * to workers. |
michael@0 | 3674 | * @var {boolean} |
michael@0 | 3675 | */ |
michael@0 | 3676 | PDFJS.disableWorker = (PDFJS.disableWorker === undefined ? |
michael@0 | 3677 | false : PDFJS.disableWorker); |
michael@0 | 3678 | |
michael@0 | 3679 | /** |
michael@0 | 3680 | * Path and filename of the worker file. Required when the worker is enabled in |
michael@0 | 3681 | * development mode. If unspecified in the production build, the worker will be |
michael@0 | 3682 | * loaded based on the location of the pdf.js file. |
michael@0 | 3683 | * @var {string} |
michael@0 | 3684 | */ |
michael@0 | 3685 | PDFJS.workerSrc = (PDFJS.workerSrc === undefined ? null : PDFJS.workerSrc); |
michael@0 | 3686 | |
michael@0 | 3687 | /** |
michael@0 | 3688 | * Disable range request loading of PDF files. When enabled and if the server |
michael@0 | 3689 | * supports partial content requests then the PDF will be fetched in chunks. |
michael@0 | 3690 | * Enabled (false) by default. |
michael@0 | 3691 | * @var {boolean} |
michael@0 | 3692 | */ |
michael@0 | 3693 | PDFJS.disableRange = (PDFJS.disableRange === undefined ? |
michael@0 | 3694 | false : PDFJS.disableRange); |
michael@0 | 3695 | |
michael@0 | 3696 | /** |
michael@0 | 3697 | * Disable pre-fetching of PDF file data. When range requests are enabled PDF.js |
michael@0 | 3698 | * will automatically keep fetching more data even if it isn't needed to display |
michael@0 | 3699 | * the current page. This default behavior can be disabled. |
michael@0 | 3700 | * @var {boolean} |
michael@0 | 3701 | */ |
michael@0 | 3702 | PDFJS.disableAutoFetch = (PDFJS.disableAutoFetch === undefined ? |
michael@0 | 3703 | false : PDFJS.disableAutoFetch); |
michael@0 | 3704 | |
michael@0 | 3705 | /** |
michael@0 | 3706 | * Enables special hooks for debugging PDF.js. |
michael@0 | 3707 | * @var {boolean} |
michael@0 | 3708 | */ |
michael@0 | 3709 | PDFJS.pdfBug = (PDFJS.pdfBug === undefined ? false : PDFJS.pdfBug); |
michael@0 | 3710 | |
michael@0 | 3711 | /** |
michael@0 | 3712 | * Enables transfer usage in postMessage for ArrayBuffers. |
michael@0 | 3713 | * @var {boolean} |
michael@0 | 3714 | */ |
michael@0 | 3715 | PDFJS.postMessageTransfers = (PDFJS.postMessageTransfers === undefined ? |
michael@0 | 3716 | true : PDFJS.postMessageTransfers); |
michael@0 | 3717 | |
michael@0 | 3718 | /** |
michael@0 | 3719 | * Disables URL.createObjectURL usage. |
michael@0 | 3720 | * @var {boolean} |
michael@0 | 3721 | */ |
michael@0 | 3722 | PDFJS.disableCreateObjectURL = (PDFJS.disableCreateObjectURL === undefined ? |
michael@0 | 3723 | false : PDFJS.disableCreateObjectURL); |
michael@0 | 3724 | |
michael@0 | 3725 | /** |
michael@0 | 3726 | * Disables WebGL usage. |
michael@0 | 3727 | * @var {boolean} |
michael@0 | 3728 | */ |
michael@0 | 3729 | PDFJS.disableWebGL = (PDFJS.disableWebGL === undefined ? |
michael@0 | 3730 | true : PDFJS.disableWebGL); |
michael@0 | 3731 | |
michael@0 | 3732 | /** |
michael@0 | 3733 | * Controls the logging level. |
michael@0 | 3734 | * The constants from PDFJS.VERBOSITY_LEVELS should be used: |
michael@0 | 3735 | * - errors |
michael@0 | 3736 | * - warnings [default] |
michael@0 | 3737 | * - infos |
michael@0 | 3738 | * @var {number} |
michael@0 | 3739 | */ |
michael@0 | 3740 | PDFJS.verbosity = (PDFJS.verbosity === undefined ? |
michael@0 | 3741 | PDFJS.VERBOSITY_LEVELS.warnings : PDFJS.verbosity); |
michael@0 | 3742 | |
michael@0 | 3743 | /** |
michael@0 | 3744 | * Document initialization / loading parameters object. |
michael@0 | 3745 | * |
michael@0 | 3746 | * @typedef {Object} DocumentInitParameters |
michael@0 | 3747 | * @property {string} url - The URL of the PDF. |
michael@0 | 3748 | * @property {TypedArray} data - A typed array with PDF data. |
michael@0 | 3749 | * @property {Object} httpHeaders - Basic authentication headers. |
michael@0 | 3750 | * @property {boolean} withCredentials - Indicates whether or not cross-site |
michael@0 | 3751 | * Access-Control requests should be made using credentials such as cookies |
michael@0 | 3752 | * or authorization headers. The default is false. |
michael@0 | 3753 | * @property {string} password - For decrypting password-protected PDFs. |
michael@0 | 3754 | * @property {TypedArray} initialData - A typed array with the first portion or |
michael@0 | 3755 | * all of the pdf data. Used by the extension since some data is already |
michael@0 | 3756 | * loaded before the switch to range requests. |
michael@0 | 3757 | */ |
michael@0 | 3758 | |
michael@0 | 3759 | /** |
michael@0 | 3760 | * This is the main entry point for loading a PDF and interacting with it. |
michael@0 | 3761 | * NOTE: If a URL is used to fetch the PDF data a standard XMLHttpRequest(XHR) |
michael@0 | 3762 | * is used, which means it must follow the same origin rules that any XHR does |
michael@0 | 3763 | * e.g. No cross domain requests without CORS. |
michael@0 | 3764 | * |
michael@0 | 3765 | * @param {string|TypedArray|DocumentInitParameters} source Can be a url to |
michael@0 | 3766 | * where a PDF is located, a typed array (Uint8Array) already populated with |
michael@0 | 3767 | * data or parameter object. |
michael@0 | 3768 | * |
michael@0 | 3769 | * @param {Object} pdfDataRangeTransport is optional. It is used if you want |
michael@0 | 3770 | * to manually serve range requests for data in the PDF. See viewer.js for |
michael@0 | 3771 | * an example of pdfDataRangeTransport's interface. |
michael@0 | 3772 | * |
michael@0 | 3773 | * @param {function} passwordCallback is optional. It is used to request a |
michael@0 | 3774 | * password if wrong or no password was provided. The callback receives two |
michael@0 | 3775 | * parameters: function that needs to be called with new password and reason |
michael@0 | 3776 | * (see {PasswordResponses}). |
michael@0 | 3777 | * |
michael@0 | 3778 | * @return {Promise} A promise that is resolved with {@link PDFDocumentProxy} |
michael@0 | 3779 | * object. |
michael@0 | 3780 | */ |
michael@0 | 3781 | PDFJS.getDocument = function getDocument(source, |
michael@0 | 3782 | pdfDataRangeTransport, |
michael@0 | 3783 | passwordCallback, |
michael@0 | 3784 | progressCallback) { |
michael@0 | 3785 | var workerInitializedPromise, workerReadyPromise, transport; |
michael@0 | 3786 | |
michael@0 | 3787 | if (typeof source === 'string') { |
michael@0 | 3788 | source = { url: source }; |
michael@0 | 3789 | } else if (isArrayBuffer(source)) { |
michael@0 | 3790 | source = { data: source }; |
michael@0 | 3791 | } else if (typeof source !== 'object') { |
michael@0 | 3792 | error('Invalid parameter in getDocument, need either Uint8Array, ' + |
michael@0 | 3793 | 'string or a parameter object'); |
michael@0 | 3794 | } |
michael@0 | 3795 | |
michael@0 | 3796 | if (!source.url && !source.data) { |
michael@0 | 3797 | error('Invalid parameter array, need either .data or .url'); |
michael@0 | 3798 | } |
michael@0 | 3799 | |
michael@0 | 3800 | // copy/use all keys as is except 'url' -- full path is required |
michael@0 | 3801 | var params = {}; |
michael@0 | 3802 | for (var key in source) { |
michael@0 | 3803 | if (key === 'url' && typeof window !== 'undefined') { |
michael@0 | 3804 | params[key] = combineUrl(window.location.href, source[key]); |
michael@0 | 3805 | continue; |
michael@0 | 3806 | } |
michael@0 | 3807 | params[key] = source[key]; |
michael@0 | 3808 | } |
michael@0 | 3809 | |
michael@0 | 3810 | workerInitializedPromise = new PDFJS.LegacyPromise(); |
michael@0 | 3811 | workerReadyPromise = new PDFJS.LegacyPromise(); |
michael@0 | 3812 | transport = new WorkerTransport(workerInitializedPromise, workerReadyPromise, |
michael@0 | 3813 | pdfDataRangeTransport, progressCallback); |
michael@0 | 3814 | workerInitializedPromise.then(function transportInitialized() { |
michael@0 | 3815 | transport.passwordCallback = passwordCallback; |
michael@0 | 3816 | transport.fetchDocument(params); |
michael@0 | 3817 | }); |
michael@0 | 3818 | return workerReadyPromise; |
michael@0 | 3819 | }; |
michael@0 | 3820 | |
michael@0 | 3821 | /** |
michael@0 | 3822 | * Proxy to a PDFDocument in the worker thread. Also, contains commonly used |
michael@0 | 3823 | * properties that can be read synchronously. |
michael@0 | 3824 | * @class |
michael@0 | 3825 | */ |
michael@0 | 3826 | var PDFDocumentProxy = (function PDFDocumentProxyClosure() { |
michael@0 | 3827 | function PDFDocumentProxy(pdfInfo, transport) { |
michael@0 | 3828 | this.pdfInfo = pdfInfo; |
michael@0 | 3829 | this.transport = transport; |
michael@0 | 3830 | } |
michael@0 | 3831 | PDFDocumentProxy.prototype = /** @lends PDFDocumentProxy.prototype */ { |
michael@0 | 3832 | /** |
michael@0 | 3833 | * @return {number} Total number of pages the PDF contains. |
michael@0 | 3834 | */ |
michael@0 | 3835 | get numPages() { |
michael@0 | 3836 | return this.pdfInfo.numPages; |
michael@0 | 3837 | }, |
michael@0 | 3838 | /** |
michael@0 | 3839 | * @return {string} A unique ID to identify a PDF. Not guaranteed to be |
michael@0 | 3840 | * unique. |
michael@0 | 3841 | */ |
michael@0 | 3842 | get fingerprint() { |
michael@0 | 3843 | return this.pdfInfo.fingerprint; |
michael@0 | 3844 | }, |
michael@0 | 3845 | /** |
michael@0 | 3846 | * @param {number} pageNumber The page number to get. The first page is 1. |
michael@0 | 3847 | * @return {Promise} A promise that is resolved with a {@link PDFPageProxy} |
michael@0 | 3848 | * object. |
michael@0 | 3849 | */ |
michael@0 | 3850 | getPage: function PDFDocumentProxy_getPage(pageNumber) { |
michael@0 | 3851 | return this.transport.getPage(pageNumber); |
michael@0 | 3852 | }, |
michael@0 | 3853 | /** |
michael@0 | 3854 | * @param {{num: number, gen: number}} ref The page reference. Must have |
michael@0 | 3855 | * the 'num' and 'gen' properties. |
michael@0 | 3856 | * @return {Promise} A promise that is resolved with the page index that is |
michael@0 | 3857 | * associated with the reference. |
michael@0 | 3858 | */ |
michael@0 | 3859 | getPageIndex: function PDFDocumentProxy_getPageIndex(ref) { |
michael@0 | 3860 | return this.transport.getPageIndex(ref); |
michael@0 | 3861 | }, |
michael@0 | 3862 | /** |
michael@0 | 3863 | * @return {Promise} A promise that is resolved with a lookup table for |
michael@0 | 3864 | * mapping named destinations to reference numbers. |
michael@0 | 3865 | */ |
michael@0 | 3866 | getDestinations: function PDFDocumentProxy_getDestinations() { |
michael@0 | 3867 | return this.transport.getDestinations(); |
michael@0 | 3868 | }, |
michael@0 | 3869 | /** |
michael@0 | 3870 | * @return {Promise} A promise that is resolved with a lookup table for |
michael@0 | 3871 | * mapping named attachments to their content. |
michael@0 | 3872 | */ |
michael@0 | 3873 | getAttachments: function PDFDocumentProxy_getAttachments() { |
michael@0 | 3874 | return this.transport.getAttachments(); |
michael@0 | 3875 | }, |
michael@0 | 3876 | /** |
michael@0 | 3877 | * @return {Promise} A promise that is resolved with an array of all the |
michael@0 | 3878 | * JavaScript strings in the name tree. |
michael@0 | 3879 | */ |
michael@0 | 3880 | getJavaScript: function PDFDocumentProxy_getJavaScript() { |
michael@0 | 3881 | var promise = new PDFJS.LegacyPromise(); |
michael@0 | 3882 | var js = this.pdfInfo.javaScript; |
michael@0 | 3883 | promise.resolve(js); |
michael@0 | 3884 | return promise; |
michael@0 | 3885 | }, |
michael@0 | 3886 | /** |
michael@0 | 3887 | * @return {Promise} A promise that is resolved with an {Array} that is a |
michael@0 | 3888 | * tree outline (if it has one) of the PDF. The tree is in the format of: |
michael@0 | 3889 | * [ |
michael@0 | 3890 | * { |
michael@0 | 3891 | * title: string, |
michael@0 | 3892 | * bold: boolean, |
michael@0 | 3893 | * italic: boolean, |
michael@0 | 3894 | * color: rgb array, |
michael@0 | 3895 | * dest: dest obj, |
michael@0 | 3896 | * items: array of more items like this |
michael@0 | 3897 | * }, |
michael@0 | 3898 | * ... |
michael@0 | 3899 | * ]. |
michael@0 | 3900 | */ |
michael@0 | 3901 | getOutline: function PDFDocumentProxy_getOutline() { |
michael@0 | 3902 | var promise = new PDFJS.LegacyPromise(); |
michael@0 | 3903 | var outline = this.pdfInfo.outline; |
michael@0 | 3904 | promise.resolve(outline); |
michael@0 | 3905 | return promise; |
michael@0 | 3906 | }, |
michael@0 | 3907 | /** |
michael@0 | 3908 | * @return {Promise} A promise that is resolved with an {Object} that has |
michael@0 | 3909 | * info and metadata properties. Info is an {Object} filled with anything |
michael@0 | 3910 | * available in the information dictionary and similarly metadata is a |
michael@0 | 3911 | * {Metadata} object with information from the metadata section of the PDF. |
michael@0 | 3912 | */ |
michael@0 | 3913 | getMetadata: function PDFDocumentProxy_getMetadata() { |
michael@0 | 3914 | var promise = new PDFJS.LegacyPromise(); |
michael@0 | 3915 | var info = this.pdfInfo.info; |
michael@0 | 3916 | var metadata = this.pdfInfo.metadata; |
michael@0 | 3917 | promise.resolve({ |
michael@0 | 3918 | info: info, |
michael@0 | 3919 | metadata: (metadata ? new PDFJS.Metadata(metadata) : null) |
michael@0 | 3920 | }); |
michael@0 | 3921 | return promise; |
michael@0 | 3922 | }, |
michael@0 | 3923 | /** |
michael@0 | 3924 | * @return {Promise} A promise that is resolved with a TypedArray that has |
michael@0 | 3925 | * the raw data from the PDF. |
michael@0 | 3926 | */ |
michael@0 | 3927 | getData: function PDFDocumentProxy_getData() { |
michael@0 | 3928 | var promise = new PDFJS.LegacyPromise(); |
michael@0 | 3929 | this.transport.getData(promise); |
michael@0 | 3930 | return promise; |
michael@0 | 3931 | }, |
michael@0 | 3932 | /** |
michael@0 | 3933 | * @return {Promise} A promise that is resolved when the document's data |
michael@0 | 3934 | * is loaded. It is resolved with an {Object} that contains the length |
michael@0 | 3935 | * property that indicates size of the PDF data in bytes. |
michael@0 | 3936 | */ |
michael@0 | 3937 | getDownloadInfo: function PDFDocumentProxy_getDownloadInfo() { |
michael@0 | 3938 | return this.transport.downloadInfoPromise; |
michael@0 | 3939 | }, |
michael@0 | 3940 | /** |
michael@0 | 3941 | * Cleans up resources allocated by the document, e.g. created @font-face. |
michael@0 | 3942 | */ |
michael@0 | 3943 | cleanup: function PDFDocumentProxy_cleanup() { |
michael@0 | 3944 | this.transport.startCleanup(); |
michael@0 | 3945 | }, |
michael@0 | 3946 | /** |
michael@0 | 3947 | * Destroys current document instance and terminates worker. |
michael@0 | 3948 | */ |
michael@0 | 3949 | destroy: function PDFDocumentProxy_destroy() { |
michael@0 | 3950 | this.transport.destroy(); |
michael@0 | 3951 | } |
michael@0 | 3952 | }; |
michael@0 | 3953 | return PDFDocumentProxy; |
michael@0 | 3954 | })(); |
michael@0 | 3955 | |
michael@0 | 3956 | /** |
michael@0 | 3957 | * Page text content. |
michael@0 | 3958 | * |
michael@0 | 3959 | * @typedef {Object} TextContent |
michael@0 | 3960 | * @property {array} items - array of {@link TextItem} |
michael@0 | 3961 | * @property {Object} styles - {@link TextStyles} objects, indexed by font |
michael@0 | 3962 | * name. |
michael@0 | 3963 | */ |
michael@0 | 3964 | |
michael@0 | 3965 | /** |
michael@0 | 3966 | * Page text content part. |
michael@0 | 3967 | * |
michael@0 | 3968 | * @typedef {Object} TextItem |
michael@0 | 3969 | * @property {string} str - text content. |
michael@0 | 3970 | * @property {string} dir - text direction: 'ttb', 'ltr' or 'rtl'. |
michael@0 | 3971 | * @property {array} transform - transformation matrix. |
michael@0 | 3972 | * @property {number} width - width in device space. |
michael@0 | 3973 | * @property {number} height - height in device space. |
michael@0 | 3974 | * @property {string} fontName - font name used by pdf.js for converted font. |
michael@0 | 3975 | */ |
michael@0 | 3976 | |
michael@0 | 3977 | /** |
michael@0 | 3978 | * Text style. |
michael@0 | 3979 | * |
michael@0 | 3980 | * @typedef {Object} TextStyle |
michael@0 | 3981 | * @property {number} ascent - font ascent. |
michael@0 | 3982 | * @property {number} descent - font descent. |
michael@0 | 3983 | * @property {boolean} vertical - text is in vertical mode. |
michael@0 | 3984 | * @property {string} fontFamily - possible font family |
michael@0 | 3985 | */ |
michael@0 | 3986 | |
michael@0 | 3987 | /** |
michael@0 | 3988 | * Page render parameters. |
michael@0 | 3989 | * |
michael@0 | 3990 | * @typedef {Object} RenderParameters |
michael@0 | 3991 | * @property {Object} canvasContext - A 2D context of a DOM Canvas object. |
michael@0 | 3992 | * @property {PageViewport} viewport - Rendering viewport obtained by |
michael@0 | 3993 | * calling of PDFPage.getViewport method. |
michael@0 | 3994 | * @property {string} intent - Rendering intent, can be 'display' or 'print' |
michael@0 | 3995 | * (default value is 'display'). |
michael@0 | 3996 | * @property {Object} imageLayer - (optional) An object that has beginLayout, |
michael@0 | 3997 | * endLayout and appendImage functions. |
michael@0 | 3998 | * @property {function} continueCallback - (optional) A function that will be |
michael@0 | 3999 | * called each time the rendering is paused. To continue |
michael@0 | 4000 | * rendering call the function that is the first argument |
michael@0 | 4001 | * to the callback. |
michael@0 | 4002 | */ |
michael@0 | 4003 | |
michael@0 | 4004 | /** |
michael@0 | 4005 | * Proxy to a PDFPage in the worker thread. |
michael@0 | 4006 | * @class |
michael@0 | 4007 | */ |
michael@0 | 4008 | var PDFPageProxy = (function PDFPageProxyClosure() { |
michael@0 | 4009 | function PDFPageProxy(pageInfo, transport) { |
michael@0 | 4010 | this.pageInfo = pageInfo; |
michael@0 | 4011 | this.transport = transport; |
michael@0 | 4012 | this.stats = new StatTimer(); |
michael@0 | 4013 | this.stats.enabled = !!globalScope.PDFJS.enableStats; |
michael@0 | 4014 | this.commonObjs = transport.commonObjs; |
michael@0 | 4015 | this.objs = new PDFObjects(); |
michael@0 | 4016 | this.cleanupAfterRender = false; |
michael@0 | 4017 | this.pendingDestroy = false; |
michael@0 | 4018 | this.intentStates = {}; |
michael@0 | 4019 | } |
michael@0 | 4020 | PDFPageProxy.prototype = /** @lends PDFPageProxy.prototype */ { |
michael@0 | 4021 | /** |
michael@0 | 4022 | * @return {number} Page number of the page. First page is 1. |
michael@0 | 4023 | */ |
michael@0 | 4024 | get pageNumber() { |
michael@0 | 4025 | return this.pageInfo.pageIndex + 1; |
michael@0 | 4026 | }, |
michael@0 | 4027 | /** |
michael@0 | 4028 | * @return {number} The number of degrees the page is rotated clockwise. |
michael@0 | 4029 | */ |
michael@0 | 4030 | get rotate() { |
michael@0 | 4031 | return this.pageInfo.rotate; |
michael@0 | 4032 | }, |
michael@0 | 4033 | /** |
michael@0 | 4034 | * @return {Object} The reference that points to this page. It has 'num' and |
michael@0 | 4035 | * 'gen' properties. |
michael@0 | 4036 | */ |
michael@0 | 4037 | get ref() { |
michael@0 | 4038 | return this.pageInfo.ref; |
michael@0 | 4039 | }, |
michael@0 | 4040 | /** |
michael@0 | 4041 | * @return {Array} An array of the visible portion of the PDF page in the |
michael@0 | 4042 | * user space units - [x1, y1, x2, y2]. |
michael@0 | 4043 | */ |
michael@0 | 4044 | get view() { |
michael@0 | 4045 | return this.pageInfo.view; |
michael@0 | 4046 | }, |
michael@0 | 4047 | /** |
michael@0 | 4048 | * @param {number} scale The desired scale of the viewport. |
michael@0 | 4049 | * @param {number} rotate Degrees to rotate the viewport. If omitted this |
michael@0 | 4050 | * defaults to the page rotation. |
michael@0 | 4051 | * @return {PageViewport} Contains 'width' and 'height' properties along |
michael@0 | 4052 | * with transforms required for rendering. |
michael@0 | 4053 | */ |
michael@0 | 4054 | getViewport: function PDFPageProxy_getViewport(scale, rotate) { |
michael@0 | 4055 | if (arguments.length < 2) { |
michael@0 | 4056 | rotate = this.rotate; |
michael@0 | 4057 | } |
michael@0 | 4058 | return new PDFJS.PageViewport(this.view, scale, rotate, 0, 0); |
michael@0 | 4059 | }, |
michael@0 | 4060 | /** |
michael@0 | 4061 | * @return {Promise} A promise that is resolved with an {Array} of the |
michael@0 | 4062 | * annotation objects. |
michael@0 | 4063 | */ |
michael@0 | 4064 | getAnnotations: function PDFPageProxy_getAnnotations() { |
michael@0 | 4065 | if (this.annotationsPromise) { |
michael@0 | 4066 | return this.annotationsPromise; |
michael@0 | 4067 | } |
michael@0 | 4068 | |
michael@0 | 4069 | var promise = new PDFJS.LegacyPromise(); |
michael@0 | 4070 | this.annotationsPromise = promise; |
michael@0 | 4071 | this.transport.getAnnotations(this.pageInfo.pageIndex); |
michael@0 | 4072 | return promise; |
michael@0 | 4073 | }, |
michael@0 | 4074 | /** |
michael@0 | 4075 | * Begins the process of rendering a page to the desired context. |
michael@0 | 4076 | * @param {RenderParameters} params Page render parameters. |
michael@0 | 4077 | * @return {RenderTask} An object that contains the promise, which |
michael@0 | 4078 | * is resolved when the page finishes rendering. |
michael@0 | 4079 | */ |
michael@0 | 4080 | render: function PDFPageProxy_render(params) { |
michael@0 | 4081 | var stats = this.stats; |
michael@0 | 4082 | stats.time('Overall'); |
michael@0 | 4083 | |
michael@0 | 4084 | // If there was a pending destroy cancel it so no cleanup happens during |
michael@0 | 4085 | // this call to render. |
michael@0 | 4086 | this.pendingDestroy = false; |
michael@0 | 4087 | |
michael@0 | 4088 | var renderingIntent = ('intent' in params ? |
michael@0 | 4089 | (params.intent == 'print' ? 'print' : 'display') : 'display'); |
michael@0 | 4090 | |
michael@0 | 4091 | if (!this.intentStates[renderingIntent]) { |
michael@0 | 4092 | this.intentStates[renderingIntent] = {}; |
michael@0 | 4093 | } |
michael@0 | 4094 | var intentState = this.intentStates[renderingIntent]; |
michael@0 | 4095 | |
michael@0 | 4096 | // If there is no displayReadyPromise yet, then the operatorList was never |
michael@0 | 4097 | // requested before. Make the request and create the promise. |
michael@0 | 4098 | if (!intentState.displayReadyPromise) { |
michael@0 | 4099 | intentState.receivingOperatorList = true; |
michael@0 | 4100 | intentState.displayReadyPromise = new LegacyPromise(); |
michael@0 | 4101 | intentState.operatorList = { |
michael@0 | 4102 | fnArray: [], |
michael@0 | 4103 | argsArray: [], |
michael@0 | 4104 | lastChunk: false |
michael@0 | 4105 | }; |
michael@0 | 4106 | |
michael@0 | 4107 | this.stats.time('Page Request'); |
michael@0 | 4108 | this.transport.messageHandler.send('RenderPageRequest', { |
michael@0 | 4109 | pageIndex: this.pageNumber - 1, |
michael@0 | 4110 | intent: renderingIntent |
michael@0 | 4111 | }); |
michael@0 | 4112 | } |
michael@0 | 4113 | |
michael@0 | 4114 | var internalRenderTask = new InternalRenderTask(complete, params, |
michael@0 | 4115 | this.objs, |
michael@0 | 4116 | this.commonObjs, |
michael@0 | 4117 | intentState.operatorList, |
michael@0 | 4118 | this.pageNumber); |
michael@0 | 4119 | if (!intentState.renderTasks) { |
michael@0 | 4120 | intentState.renderTasks = []; |
michael@0 | 4121 | } |
michael@0 | 4122 | intentState.renderTasks.push(internalRenderTask); |
michael@0 | 4123 | var renderTask = new RenderTask(internalRenderTask); |
michael@0 | 4124 | |
michael@0 | 4125 | var self = this; |
michael@0 | 4126 | intentState.displayReadyPromise.then( |
michael@0 | 4127 | function pageDisplayReadyPromise(transparency) { |
michael@0 | 4128 | if (self.pendingDestroy) { |
michael@0 | 4129 | complete(); |
michael@0 | 4130 | return; |
michael@0 | 4131 | } |
michael@0 | 4132 | stats.time('Rendering'); |
michael@0 | 4133 | internalRenderTask.initalizeGraphics(transparency); |
michael@0 | 4134 | internalRenderTask.operatorListChanged(); |
michael@0 | 4135 | }, |
michael@0 | 4136 | function pageDisplayReadPromiseError(reason) { |
michael@0 | 4137 | complete(reason); |
michael@0 | 4138 | } |
michael@0 | 4139 | ); |
michael@0 | 4140 | |
michael@0 | 4141 | function complete(error) { |
michael@0 | 4142 | var i = intentState.renderTasks.indexOf(internalRenderTask); |
michael@0 | 4143 | if (i >= 0) { |
michael@0 | 4144 | intentState.renderTasks.splice(i, 1); |
michael@0 | 4145 | } |
michael@0 | 4146 | |
michael@0 | 4147 | if (self.cleanupAfterRender) { |
michael@0 | 4148 | self.pendingDestroy = true; |
michael@0 | 4149 | } |
michael@0 | 4150 | self._tryDestroy(); |
michael@0 | 4151 | |
michael@0 | 4152 | if (error) { |
michael@0 | 4153 | renderTask.promise.reject(error); |
michael@0 | 4154 | } else { |
michael@0 | 4155 | renderTask.promise.resolve(); |
michael@0 | 4156 | } |
michael@0 | 4157 | stats.timeEnd('Rendering'); |
michael@0 | 4158 | stats.timeEnd('Overall'); |
michael@0 | 4159 | } |
michael@0 | 4160 | |
michael@0 | 4161 | return renderTask; |
michael@0 | 4162 | }, |
michael@0 | 4163 | /** |
michael@0 | 4164 | * @return {Promise} That is resolved a {@link TextContent} |
michael@0 | 4165 | * object that represent the page text content. |
michael@0 | 4166 | */ |
michael@0 | 4167 | getTextContent: function PDFPageProxy_getTextContent() { |
michael@0 | 4168 | var promise = new PDFJS.LegacyPromise(); |
michael@0 | 4169 | this.transport.messageHandler.send('GetTextContent', { |
michael@0 | 4170 | pageIndex: this.pageNumber - 1 |
michael@0 | 4171 | }, |
michael@0 | 4172 | function textContentCallback(textContent) { |
michael@0 | 4173 | promise.resolve(textContent); |
michael@0 | 4174 | } |
michael@0 | 4175 | ); |
michael@0 | 4176 | return promise; |
michael@0 | 4177 | }, |
michael@0 | 4178 | /** |
michael@0 | 4179 | * Destroys resources allocated by the page. |
michael@0 | 4180 | */ |
michael@0 | 4181 | destroy: function PDFPageProxy_destroy() { |
michael@0 | 4182 | this.pendingDestroy = true; |
michael@0 | 4183 | this._tryDestroy(); |
michael@0 | 4184 | }, |
michael@0 | 4185 | /** |
michael@0 | 4186 | * For internal use only. Attempts to clean up if rendering is in a state |
michael@0 | 4187 | * where that's possible. |
michael@0 | 4188 | * @ignore |
michael@0 | 4189 | */ |
michael@0 | 4190 | _tryDestroy: function PDFPageProxy__destroy() { |
michael@0 | 4191 | if (!this.pendingDestroy || |
michael@0 | 4192 | Object.keys(this.intentStates).some(function(intent) { |
michael@0 | 4193 | var intentState = this.intentStates[intent]; |
michael@0 | 4194 | return (intentState.renderTasks.length !== 0 || |
michael@0 | 4195 | intentState.receivingOperatorList); |
michael@0 | 4196 | }, this)) { |
michael@0 | 4197 | return; |
michael@0 | 4198 | } |
michael@0 | 4199 | |
michael@0 | 4200 | Object.keys(this.intentStates).forEach(function(intent) { |
michael@0 | 4201 | delete this.intentStates[intent]; |
michael@0 | 4202 | }, this); |
michael@0 | 4203 | this.objs.clear(); |
michael@0 | 4204 | this.pendingDestroy = false; |
michael@0 | 4205 | }, |
michael@0 | 4206 | /** |
michael@0 | 4207 | * For internal use only. |
michael@0 | 4208 | * @ignore |
michael@0 | 4209 | */ |
michael@0 | 4210 | _startRenderPage: function PDFPageProxy_startRenderPage(transparency, |
michael@0 | 4211 | intent) { |
michael@0 | 4212 | var intentState = this.intentStates[intent]; |
michael@0 | 4213 | intentState.displayReadyPromise.resolve(transparency); |
michael@0 | 4214 | }, |
michael@0 | 4215 | /** |
michael@0 | 4216 | * For internal use only. |
michael@0 | 4217 | * @ignore |
michael@0 | 4218 | */ |
michael@0 | 4219 | _renderPageChunk: function PDFPageProxy_renderPageChunk(operatorListChunk, |
michael@0 | 4220 | intent) { |
michael@0 | 4221 | var intentState = this.intentStates[intent]; |
michael@0 | 4222 | var i, ii; |
michael@0 | 4223 | // Add the new chunk to the current operator list. |
michael@0 | 4224 | for (i = 0, ii = operatorListChunk.length; i < ii; i++) { |
michael@0 | 4225 | intentState.operatorList.fnArray.push(operatorListChunk.fnArray[i]); |
michael@0 | 4226 | intentState.operatorList.argsArray.push( |
michael@0 | 4227 | operatorListChunk.argsArray[i]); |
michael@0 | 4228 | } |
michael@0 | 4229 | intentState.operatorList.lastChunk = operatorListChunk.lastChunk; |
michael@0 | 4230 | |
michael@0 | 4231 | // Notify all the rendering tasks there are more operators to be consumed. |
michael@0 | 4232 | for (i = 0; i < intentState.renderTasks.length; i++) { |
michael@0 | 4233 | intentState.renderTasks[i].operatorListChanged(); |
michael@0 | 4234 | } |
michael@0 | 4235 | |
michael@0 | 4236 | if (operatorListChunk.lastChunk) { |
michael@0 | 4237 | intentState.receivingOperatorList = false; |
michael@0 | 4238 | this._tryDestroy(); |
michael@0 | 4239 | } |
michael@0 | 4240 | } |
michael@0 | 4241 | }; |
michael@0 | 4242 | return PDFPageProxy; |
michael@0 | 4243 | })(); |
michael@0 | 4244 | |
michael@0 | 4245 | /** |
michael@0 | 4246 | * For internal use only. |
michael@0 | 4247 | * @ignore |
michael@0 | 4248 | */ |
michael@0 | 4249 | var WorkerTransport = (function WorkerTransportClosure() { |
michael@0 | 4250 | function WorkerTransport(workerInitializedPromise, workerReadyPromise, |
michael@0 | 4251 | pdfDataRangeTransport, progressCallback) { |
michael@0 | 4252 | this.pdfDataRangeTransport = pdfDataRangeTransport; |
michael@0 | 4253 | |
michael@0 | 4254 | this.workerReadyPromise = workerReadyPromise; |
michael@0 | 4255 | this.progressCallback = progressCallback; |
michael@0 | 4256 | this.commonObjs = new PDFObjects(); |
michael@0 | 4257 | |
michael@0 | 4258 | this.pageCache = []; |
michael@0 | 4259 | this.pagePromises = []; |
michael@0 | 4260 | this.downloadInfoPromise = new PDFJS.LegacyPromise(); |
michael@0 | 4261 | this.passwordCallback = null; |
michael@0 | 4262 | |
michael@0 | 4263 | // If worker support isn't disabled explicit and the browser has worker |
michael@0 | 4264 | // support, create a new web worker and test if it/the browser fullfills |
michael@0 | 4265 | // all requirements to run parts of pdf.js in a web worker. |
michael@0 | 4266 | // Right now, the requirement is, that an Uint8Array is still an Uint8Array |
michael@0 | 4267 | // as it arrives on the worker. Chrome added this with version 15. |
michael@0 | 4268 | if (!globalScope.PDFJS.disableWorker && typeof Worker !== 'undefined') { |
michael@0 | 4269 | var workerSrc = PDFJS.workerSrc; |
michael@0 | 4270 | if (!workerSrc) { |
michael@0 | 4271 | error('No PDFJS.workerSrc specified'); |
michael@0 | 4272 | } |
michael@0 | 4273 | |
michael@0 | 4274 | try { |
michael@0 | 4275 | // Some versions of FF can't create a worker on localhost, see: |
michael@0 | 4276 | // https://bugzilla.mozilla.org/show_bug.cgi?id=683280 |
michael@0 | 4277 | var worker = new Worker(workerSrc); |
michael@0 | 4278 | var messageHandler = new MessageHandler('main', worker); |
michael@0 | 4279 | this.messageHandler = messageHandler; |
michael@0 | 4280 | |
michael@0 | 4281 | messageHandler.on('test', function transportTest(data) { |
michael@0 | 4282 | var supportTypedArray = data && data.supportTypedArray; |
michael@0 | 4283 | if (supportTypedArray) { |
michael@0 | 4284 | this.worker = worker; |
michael@0 | 4285 | if (!data.supportTransfers) { |
michael@0 | 4286 | PDFJS.postMessageTransfers = false; |
michael@0 | 4287 | } |
michael@0 | 4288 | this.setupMessageHandler(messageHandler); |
michael@0 | 4289 | workerInitializedPromise.resolve(); |
michael@0 | 4290 | } else { |
michael@0 | 4291 | globalScope.PDFJS.disableWorker = true; |
michael@0 | 4292 | this.loadFakeWorkerFiles().then(function() { |
michael@0 | 4293 | this.setupFakeWorker(); |
michael@0 | 4294 | workerInitializedPromise.resolve(); |
michael@0 | 4295 | }.bind(this)); |
michael@0 | 4296 | } |
michael@0 | 4297 | }.bind(this)); |
michael@0 | 4298 | |
michael@0 | 4299 | var testObj = new Uint8Array([PDFJS.postMessageTransfers ? 255 : 0]); |
michael@0 | 4300 | // Some versions of Opera throw a DATA_CLONE_ERR on serializing the |
michael@0 | 4301 | // typed array. Also, checking if we can use transfers. |
michael@0 | 4302 | try { |
michael@0 | 4303 | messageHandler.send('test', testObj, null, [testObj.buffer]); |
michael@0 | 4304 | } catch (ex) { |
michael@0 | 4305 | info('Cannot use postMessage transfers'); |
michael@0 | 4306 | testObj[0] = 0; |
michael@0 | 4307 | messageHandler.send('test', testObj); |
michael@0 | 4308 | } |
michael@0 | 4309 | return; |
michael@0 | 4310 | } catch (e) { |
michael@0 | 4311 | info('The worker has been disabled.'); |
michael@0 | 4312 | } |
michael@0 | 4313 | } |
michael@0 | 4314 | // Either workers are disabled, not supported or have thrown an exception. |
michael@0 | 4315 | // Thus, we fallback to a faked worker. |
michael@0 | 4316 | globalScope.PDFJS.disableWorker = true; |
michael@0 | 4317 | this.loadFakeWorkerFiles().then(function() { |
michael@0 | 4318 | this.setupFakeWorker(); |
michael@0 | 4319 | workerInitializedPromise.resolve(); |
michael@0 | 4320 | }.bind(this)); |
michael@0 | 4321 | } |
michael@0 | 4322 | WorkerTransport.prototype = { |
michael@0 | 4323 | destroy: function WorkerTransport_destroy() { |
michael@0 | 4324 | this.pageCache = []; |
michael@0 | 4325 | this.pagePromises = []; |
michael@0 | 4326 | var self = this; |
michael@0 | 4327 | this.messageHandler.send('Terminate', null, function () { |
michael@0 | 4328 | FontLoader.clear(); |
michael@0 | 4329 | if (self.worker) { |
michael@0 | 4330 | self.worker.terminate(); |
michael@0 | 4331 | } |
michael@0 | 4332 | }); |
michael@0 | 4333 | }, |
michael@0 | 4334 | |
michael@0 | 4335 | loadFakeWorkerFiles: function WorkerTransport_loadFakeWorkerFiles() { |
michael@0 | 4336 | if (!PDFJS.fakeWorkerFilesLoadedPromise) { |
michael@0 | 4337 | PDFJS.fakeWorkerFilesLoadedPromise = new LegacyPromise(); |
michael@0 | 4338 | // In the developer build load worker_loader which in turn loads all the |
michael@0 | 4339 | // other files and resolves the promise. In production only the |
michael@0 | 4340 | // pdf.worker.js file is needed. |
michael@0 | 4341 | Util.loadScript(PDFJS.workerSrc, function() { |
michael@0 | 4342 | PDFJS.fakeWorkerFilesLoadedPromise.resolve(); |
michael@0 | 4343 | }); |
michael@0 | 4344 | } |
michael@0 | 4345 | return PDFJS.fakeWorkerFilesLoadedPromise; |
michael@0 | 4346 | }, |
michael@0 | 4347 | |
michael@0 | 4348 | setupFakeWorker: function WorkerTransport_setupFakeWorker() { |
michael@0 | 4349 | warn('Setting up fake worker.'); |
michael@0 | 4350 | // If we don't use a worker, just post/sendMessage to the main thread. |
michael@0 | 4351 | var fakeWorker = { |
michael@0 | 4352 | postMessage: function WorkerTransport_postMessage(obj) { |
michael@0 | 4353 | fakeWorker.onmessage({data: obj}); |
michael@0 | 4354 | }, |
michael@0 | 4355 | terminate: function WorkerTransport_terminate() {} |
michael@0 | 4356 | }; |
michael@0 | 4357 | |
michael@0 | 4358 | var messageHandler = new MessageHandler('main', fakeWorker); |
michael@0 | 4359 | this.setupMessageHandler(messageHandler); |
michael@0 | 4360 | |
michael@0 | 4361 | // If the main thread is our worker, setup the handling for the messages |
michael@0 | 4362 | // the main thread sends to it self. |
michael@0 | 4363 | PDFJS.WorkerMessageHandler.setup(messageHandler); |
michael@0 | 4364 | }, |
michael@0 | 4365 | |
michael@0 | 4366 | setupMessageHandler: |
michael@0 | 4367 | function WorkerTransport_setupMessageHandler(messageHandler) { |
michael@0 | 4368 | this.messageHandler = messageHandler; |
michael@0 | 4369 | |
michael@0 | 4370 | function updatePassword(password) { |
michael@0 | 4371 | messageHandler.send('UpdatePassword', password); |
michael@0 | 4372 | } |
michael@0 | 4373 | |
michael@0 | 4374 | var pdfDataRangeTransport = this.pdfDataRangeTransport; |
michael@0 | 4375 | if (pdfDataRangeTransport) { |
michael@0 | 4376 | pdfDataRangeTransport.addRangeListener(function(begin, chunk) { |
michael@0 | 4377 | messageHandler.send('OnDataRange', { |
michael@0 | 4378 | begin: begin, |
michael@0 | 4379 | chunk: chunk |
michael@0 | 4380 | }); |
michael@0 | 4381 | }); |
michael@0 | 4382 | |
michael@0 | 4383 | pdfDataRangeTransport.addProgressListener(function(loaded) { |
michael@0 | 4384 | messageHandler.send('OnDataProgress', { |
michael@0 | 4385 | loaded: loaded |
michael@0 | 4386 | }); |
michael@0 | 4387 | }); |
michael@0 | 4388 | |
michael@0 | 4389 | messageHandler.on('RequestDataRange', |
michael@0 | 4390 | function transportDataRange(data) { |
michael@0 | 4391 | pdfDataRangeTransport.requestDataRange(data.begin, data.end); |
michael@0 | 4392 | }, this); |
michael@0 | 4393 | } |
michael@0 | 4394 | |
michael@0 | 4395 | messageHandler.on('GetDoc', function transportDoc(data) { |
michael@0 | 4396 | var pdfInfo = data.pdfInfo; |
michael@0 | 4397 | this.numPages = data.pdfInfo.numPages; |
michael@0 | 4398 | var pdfDocument = new PDFDocumentProxy(pdfInfo, this); |
michael@0 | 4399 | this.pdfDocument = pdfDocument; |
michael@0 | 4400 | this.workerReadyPromise.resolve(pdfDocument); |
michael@0 | 4401 | }, this); |
michael@0 | 4402 | |
michael@0 | 4403 | messageHandler.on('NeedPassword', function transportPassword(data) { |
michael@0 | 4404 | if (this.passwordCallback) { |
michael@0 | 4405 | return this.passwordCallback(updatePassword, |
michael@0 | 4406 | PasswordResponses.NEED_PASSWORD); |
michael@0 | 4407 | } |
michael@0 | 4408 | this.workerReadyPromise.reject(data.exception.message, data.exception); |
michael@0 | 4409 | }, this); |
michael@0 | 4410 | |
michael@0 | 4411 | messageHandler.on('IncorrectPassword', function transportBadPass(data) { |
michael@0 | 4412 | if (this.passwordCallback) { |
michael@0 | 4413 | return this.passwordCallback(updatePassword, |
michael@0 | 4414 | PasswordResponses.INCORRECT_PASSWORD); |
michael@0 | 4415 | } |
michael@0 | 4416 | this.workerReadyPromise.reject(data.exception.message, data.exception); |
michael@0 | 4417 | }, this); |
michael@0 | 4418 | |
michael@0 | 4419 | messageHandler.on('InvalidPDF', function transportInvalidPDF(data) { |
michael@0 | 4420 | this.workerReadyPromise.reject(data.exception.name, data.exception); |
michael@0 | 4421 | }, this); |
michael@0 | 4422 | |
michael@0 | 4423 | messageHandler.on('MissingPDF', function transportMissingPDF(data) { |
michael@0 | 4424 | this.workerReadyPromise.reject(data.exception.message, data.exception); |
michael@0 | 4425 | }, this); |
michael@0 | 4426 | |
michael@0 | 4427 | messageHandler.on('UnknownError', function transportUnknownError(data) { |
michael@0 | 4428 | this.workerReadyPromise.reject(data.exception.message, data.exception); |
michael@0 | 4429 | }, this); |
michael@0 | 4430 | |
michael@0 | 4431 | messageHandler.on('DataLoaded', function transportPage(data) { |
michael@0 | 4432 | this.downloadInfoPromise.resolve(data); |
michael@0 | 4433 | }, this); |
michael@0 | 4434 | |
michael@0 | 4435 | messageHandler.on('GetPage', function transportPage(data) { |
michael@0 | 4436 | var pageInfo = data.pageInfo; |
michael@0 | 4437 | var page = new PDFPageProxy(pageInfo, this); |
michael@0 | 4438 | this.pageCache[pageInfo.pageIndex] = page; |
michael@0 | 4439 | var promise = this.pagePromises[pageInfo.pageIndex]; |
michael@0 | 4440 | promise.resolve(page); |
michael@0 | 4441 | }, this); |
michael@0 | 4442 | |
michael@0 | 4443 | messageHandler.on('GetAnnotations', function transportAnnotations(data) { |
michael@0 | 4444 | var annotations = data.annotations; |
michael@0 | 4445 | var promise = this.pageCache[data.pageIndex].annotationsPromise; |
michael@0 | 4446 | promise.resolve(annotations); |
michael@0 | 4447 | }, this); |
michael@0 | 4448 | |
michael@0 | 4449 | messageHandler.on('StartRenderPage', function transportRender(data) { |
michael@0 | 4450 | var page = this.pageCache[data.pageIndex]; |
michael@0 | 4451 | |
michael@0 | 4452 | page.stats.timeEnd('Page Request'); |
michael@0 | 4453 | page._startRenderPage(data.transparency, data.intent); |
michael@0 | 4454 | }, this); |
michael@0 | 4455 | |
michael@0 | 4456 | messageHandler.on('RenderPageChunk', function transportRender(data) { |
michael@0 | 4457 | var page = this.pageCache[data.pageIndex]; |
michael@0 | 4458 | |
michael@0 | 4459 | page._renderPageChunk(data.operatorList, data.intent); |
michael@0 | 4460 | }, this); |
michael@0 | 4461 | |
michael@0 | 4462 | messageHandler.on('commonobj', function transportObj(data) { |
michael@0 | 4463 | var id = data[0]; |
michael@0 | 4464 | var type = data[1]; |
michael@0 | 4465 | if (this.commonObjs.hasData(id)) { |
michael@0 | 4466 | return; |
michael@0 | 4467 | } |
michael@0 | 4468 | |
michael@0 | 4469 | switch (type) { |
michael@0 | 4470 | case 'Font': |
michael@0 | 4471 | var exportedData = data[2]; |
michael@0 | 4472 | |
michael@0 | 4473 | var font; |
michael@0 | 4474 | if ('error' in exportedData) { |
michael@0 | 4475 | var error = exportedData.error; |
michael@0 | 4476 | warn('Error during font loading: ' + error); |
michael@0 | 4477 | this.commonObjs.resolve(id, error); |
michael@0 | 4478 | break; |
michael@0 | 4479 | } else { |
michael@0 | 4480 | font = new FontFace(exportedData); |
michael@0 | 4481 | } |
michael@0 | 4482 | |
michael@0 | 4483 | FontLoader.bind( |
michael@0 | 4484 | [font], |
michael@0 | 4485 | function fontReady(fontObjs) { |
michael@0 | 4486 | this.commonObjs.resolve(id, font); |
michael@0 | 4487 | }.bind(this) |
michael@0 | 4488 | ); |
michael@0 | 4489 | break; |
michael@0 | 4490 | case 'FontPath': |
michael@0 | 4491 | this.commonObjs.resolve(id, data[2]); |
michael@0 | 4492 | break; |
michael@0 | 4493 | default: |
michael@0 | 4494 | error('Got unknown common object type ' + type); |
michael@0 | 4495 | } |
michael@0 | 4496 | }, this); |
michael@0 | 4497 | |
michael@0 | 4498 | messageHandler.on('obj', function transportObj(data) { |
michael@0 | 4499 | var id = data[0]; |
michael@0 | 4500 | var pageIndex = data[1]; |
michael@0 | 4501 | var type = data[2]; |
michael@0 | 4502 | var pageProxy = this.pageCache[pageIndex]; |
michael@0 | 4503 | var imageData; |
michael@0 | 4504 | if (pageProxy.objs.hasData(id)) { |
michael@0 | 4505 | return; |
michael@0 | 4506 | } |
michael@0 | 4507 | |
michael@0 | 4508 | switch (type) { |
michael@0 | 4509 | case 'JpegStream': |
michael@0 | 4510 | imageData = data[3]; |
michael@0 | 4511 | loadJpegStream(id, imageData, pageProxy.objs); |
michael@0 | 4512 | break; |
michael@0 | 4513 | case 'Image': |
michael@0 | 4514 | imageData = data[3]; |
michael@0 | 4515 | pageProxy.objs.resolve(id, imageData); |
michael@0 | 4516 | |
michael@0 | 4517 | // heuristics that will allow not to store large data |
michael@0 | 4518 | var MAX_IMAGE_SIZE_TO_STORE = 8000000; |
michael@0 | 4519 | if (imageData && 'data' in imageData && |
michael@0 | 4520 | imageData.data.length > MAX_IMAGE_SIZE_TO_STORE) { |
michael@0 | 4521 | pageProxy.cleanupAfterRender = true; |
michael@0 | 4522 | } |
michael@0 | 4523 | break; |
michael@0 | 4524 | default: |
michael@0 | 4525 | error('Got unknown object type ' + type); |
michael@0 | 4526 | } |
michael@0 | 4527 | }, this); |
michael@0 | 4528 | |
michael@0 | 4529 | messageHandler.on('DocProgress', function transportDocProgress(data) { |
michael@0 | 4530 | if (this.progressCallback) { |
michael@0 | 4531 | this.progressCallback({ |
michael@0 | 4532 | loaded: data.loaded, |
michael@0 | 4533 | total: data.total |
michael@0 | 4534 | }); |
michael@0 | 4535 | } |
michael@0 | 4536 | }, this); |
michael@0 | 4537 | |
michael@0 | 4538 | messageHandler.on('DocError', function transportDocError(data) { |
michael@0 | 4539 | this.workerReadyPromise.reject(data); |
michael@0 | 4540 | }, this); |
michael@0 | 4541 | |
michael@0 | 4542 | messageHandler.on('PageError', function transportError(data, intent) { |
michael@0 | 4543 | var page = this.pageCache[data.pageNum - 1]; |
michael@0 | 4544 | var intentState = page.intentStates[intent]; |
michael@0 | 4545 | if (intentState.displayReadyPromise) { |
michael@0 | 4546 | intentState.displayReadyPromise.reject(data.error); |
michael@0 | 4547 | } else { |
michael@0 | 4548 | error(data.error); |
michael@0 | 4549 | } |
michael@0 | 4550 | }, this); |
michael@0 | 4551 | |
michael@0 | 4552 | messageHandler.on('JpegDecode', function(data, deferred) { |
michael@0 | 4553 | var imageUrl = data[0]; |
michael@0 | 4554 | var components = data[1]; |
michael@0 | 4555 | if (components != 3 && components != 1) { |
michael@0 | 4556 | error('Only 3 component or 1 component can be returned'); |
michael@0 | 4557 | } |
michael@0 | 4558 | |
michael@0 | 4559 | var img = new Image(); |
michael@0 | 4560 | img.onload = (function messageHandler_onloadClosure() { |
michael@0 | 4561 | var width = img.width; |
michael@0 | 4562 | var height = img.height; |
michael@0 | 4563 | var size = width * height; |
michael@0 | 4564 | var rgbaLength = size * 4; |
michael@0 | 4565 | var buf = new Uint8Array(size * components); |
michael@0 | 4566 | var tmpCanvas = createScratchCanvas(width, height); |
michael@0 | 4567 | var tmpCtx = tmpCanvas.getContext('2d'); |
michael@0 | 4568 | tmpCtx.drawImage(img, 0, 0); |
michael@0 | 4569 | var data = tmpCtx.getImageData(0, 0, width, height).data; |
michael@0 | 4570 | var i, j; |
michael@0 | 4571 | |
michael@0 | 4572 | if (components == 3) { |
michael@0 | 4573 | for (i = 0, j = 0; i < rgbaLength; i += 4, j += 3) { |
michael@0 | 4574 | buf[j] = data[i]; |
michael@0 | 4575 | buf[j + 1] = data[i + 1]; |
michael@0 | 4576 | buf[j + 2] = data[i + 2]; |
michael@0 | 4577 | } |
michael@0 | 4578 | } else if (components == 1) { |
michael@0 | 4579 | for (i = 0, j = 0; i < rgbaLength; i += 4, j++) { |
michael@0 | 4580 | buf[j] = data[i]; |
michael@0 | 4581 | } |
michael@0 | 4582 | } |
michael@0 | 4583 | deferred.resolve({ data: buf, width: width, height: height}); |
michael@0 | 4584 | }).bind(this); |
michael@0 | 4585 | img.src = imageUrl; |
michael@0 | 4586 | }); |
michael@0 | 4587 | }, |
michael@0 | 4588 | |
michael@0 | 4589 | fetchDocument: function WorkerTransport_fetchDocument(source) { |
michael@0 | 4590 | source.disableAutoFetch = PDFJS.disableAutoFetch; |
michael@0 | 4591 | source.chunkedViewerLoading = !!this.pdfDataRangeTransport; |
michael@0 | 4592 | this.messageHandler.send('GetDocRequest', { |
michael@0 | 4593 | source: source, |
michael@0 | 4594 | disableRange: PDFJS.disableRange, |
michael@0 | 4595 | maxImageSize: PDFJS.maxImageSize, |
michael@0 | 4596 | cMapUrl: PDFJS.cMapUrl, |
michael@0 | 4597 | cMapPacked: PDFJS.cMapPacked, |
michael@0 | 4598 | disableFontFace: PDFJS.disableFontFace, |
michael@0 | 4599 | disableCreateObjectURL: PDFJS.disableCreateObjectURL, |
michael@0 | 4600 | verbosity: PDFJS.verbosity |
michael@0 | 4601 | }); |
michael@0 | 4602 | }, |
michael@0 | 4603 | |
michael@0 | 4604 | getData: function WorkerTransport_getData(promise) { |
michael@0 | 4605 | this.messageHandler.send('GetData', null, function(data) { |
michael@0 | 4606 | promise.resolve(data); |
michael@0 | 4607 | }); |
michael@0 | 4608 | }, |
michael@0 | 4609 | |
michael@0 | 4610 | getPage: function WorkerTransport_getPage(pageNumber, promise) { |
michael@0 | 4611 | if (pageNumber <= 0 || pageNumber > this.numPages || |
michael@0 | 4612 | (pageNumber|0) !== pageNumber) { |
michael@0 | 4613 | var pagePromise = new PDFJS.LegacyPromise(); |
michael@0 | 4614 | pagePromise.reject(new Error('Invalid page request')); |
michael@0 | 4615 | return pagePromise; |
michael@0 | 4616 | } |
michael@0 | 4617 | |
michael@0 | 4618 | var pageIndex = pageNumber - 1; |
michael@0 | 4619 | if (pageIndex in this.pagePromises) { |
michael@0 | 4620 | return this.pagePromises[pageIndex]; |
michael@0 | 4621 | } |
michael@0 | 4622 | promise = new PDFJS.LegacyPromise(); |
michael@0 | 4623 | this.pagePromises[pageIndex] = promise; |
michael@0 | 4624 | this.messageHandler.send('GetPageRequest', { pageIndex: pageIndex }); |
michael@0 | 4625 | return promise; |
michael@0 | 4626 | }, |
michael@0 | 4627 | |
michael@0 | 4628 | getPageIndex: function WorkerTransport_getPageIndexByRef(ref) { |
michael@0 | 4629 | var promise = new PDFJS.LegacyPromise(); |
michael@0 | 4630 | this.messageHandler.send('GetPageIndex', { ref: ref }, |
michael@0 | 4631 | function (pageIndex) { |
michael@0 | 4632 | promise.resolve(pageIndex); |
michael@0 | 4633 | } |
michael@0 | 4634 | ); |
michael@0 | 4635 | return promise; |
michael@0 | 4636 | }, |
michael@0 | 4637 | |
michael@0 | 4638 | getAnnotations: function WorkerTransport_getAnnotations(pageIndex) { |
michael@0 | 4639 | this.messageHandler.send('GetAnnotationsRequest', |
michael@0 | 4640 | { pageIndex: pageIndex }); |
michael@0 | 4641 | }, |
michael@0 | 4642 | |
michael@0 | 4643 | getDestinations: function WorkerTransport_getDestinations() { |
michael@0 | 4644 | var promise = new PDFJS.LegacyPromise(); |
michael@0 | 4645 | this.messageHandler.send('GetDestinations', null, |
michael@0 | 4646 | function transportDestinations(destinations) { |
michael@0 | 4647 | promise.resolve(destinations); |
michael@0 | 4648 | } |
michael@0 | 4649 | ); |
michael@0 | 4650 | return promise; |
michael@0 | 4651 | }, |
michael@0 | 4652 | |
michael@0 | 4653 | getAttachments: function WorkerTransport_getAttachments() { |
michael@0 | 4654 | var promise = new PDFJS.LegacyPromise(); |
michael@0 | 4655 | this.messageHandler.send('GetAttachments', null, |
michael@0 | 4656 | function transportAttachments(attachments) { |
michael@0 | 4657 | promise.resolve(attachments); |
michael@0 | 4658 | } |
michael@0 | 4659 | ); |
michael@0 | 4660 | return promise; |
michael@0 | 4661 | }, |
michael@0 | 4662 | |
michael@0 | 4663 | startCleanup: function WorkerTransport_startCleanup() { |
michael@0 | 4664 | this.messageHandler.send('Cleanup', null, |
michael@0 | 4665 | function endCleanup() { |
michael@0 | 4666 | for (var i = 0, ii = this.pageCache.length; i < ii; i++) { |
michael@0 | 4667 | var page = this.pageCache[i]; |
michael@0 | 4668 | if (page) { |
michael@0 | 4669 | page.destroy(); |
michael@0 | 4670 | } |
michael@0 | 4671 | } |
michael@0 | 4672 | this.commonObjs.clear(); |
michael@0 | 4673 | FontLoader.clear(); |
michael@0 | 4674 | }.bind(this) |
michael@0 | 4675 | ); |
michael@0 | 4676 | } |
michael@0 | 4677 | }; |
michael@0 | 4678 | return WorkerTransport; |
michael@0 | 4679 | |
michael@0 | 4680 | })(); |
michael@0 | 4681 | |
michael@0 | 4682 | /** |
michael@0 | 4683 | * A PDF document and page is built of many objects. E.g. there are objects |
michael@0 | 4684 | * for fonts, images, rendering code and such. These objects might get processed |
michael@0 | 4685 | * inside of a worker. The `PDFObjects` implements some basic functions to |
michael@0 | 4686 | * manage these objects. |
michael@0 | 4687 | * @ignore |
michael@0 | 4688 | */ |
michael@0 | 4689 | var PDFObjects = (function PDFObjectsClosure() { |
michael@0 | 4690 | function PDFObjects() { |
michael@0 | 4691 | this.objs = {}; |
michael@0 | 4692 | } |
michael@0 | 4693 | |
michael@0 | 4694 | PDFObjects.prototype = { |
michael@0 | 4695 | /** |
michael@0 | 4696 | * Internal function. |
michael@0 | 4697 | * Ensures there is an object defined for `objId`. |
michael@0 | 4698 | */ |
michael@0 | 4699 | ensureObj: function PDFObjects_ensureObj(objId) { |
michael@0 | 4700 | if (this.objs[objId]) { |
michael@0 | 4701 | return this.objs[objId]; |
michael@0 | 4702 | } |
michael@0 | 4703 | |
michael@0 | 4704 | var obj = { |
michael@0 | 4705 | promise: new LegacyPromise(), |
michael@0 | 4706 | data: null, |
michael@0 | 4707 | resolved: false |
michael@0 | 4708 | }; |
michael@0 | 4709 | this.objs[objId] = obj; |
michael@0 | 4710 | |
michael@0 | 4711 | return obj; |
michael@0 | 4712 | }, |
michael@0 | 4713 | |
michael@0 | 4714 | /** |
michael@0 | 4715 | * If called *without* callback, this returns the data of `objId` but the |
michael@0 | 4716 | * object needs to be resolved. If it isn't, this function throws. |
michael@0 | 4717 | * |
michael@0 | 4718 | * If called *with* a callback, the callback is called with the data of the |
michael@0 | 4719 | * object once the object is resolved. That means, if you call this |
michael@0 | 4720 | * function and the object is already resolved, the callback gets called |
michael@0 | 4721 | * right away. |
michael@0 | 4722 | */ |
michael@0 | 4723 | get: function PDFObjects_get(objId, callback) { |
michael@0 | 4724 | // If there is a callback, then the get can be async and the object is |
michael@0 | 4725 | // not required to be resolved right now |
michael@0 | 4726 | if (callback) { |
michael@0 | 4727 | this.ensureObj(objId).promise.then(callback); |
michael@0 | 4728 | return null; |
michael@0 | 4729 | } |
michael@0 | 4730 | |
michael@0 | 4731 | // If there isn't a callback, the user expects to get the resolved data |
michael@0 | 4732 | // directly. |
michael@0 | 4733 | var obj = this.objs[objId]; |
michael@0 | 4734 | |
michael@0 | 4735 | // If there isn't an object yet or the object isn't resolved, then the |
michael@0 | 4736 | // data isn't ready yet! |
michael@0 | 4737 | if (!obj || !obj.resolved) { |
michael@0 | 4738 | error('Requesting object that isn\'t resolved yet ' + objId); |
michael@0 | 4739 | } |
michael@0 | 4740 | |
michael@0 | 4741 | return obj.data; |
michael@0 | 4742 | }, |
michael@0 | 4743 | |
michael@0 | 4744 | /** |
michael@0 | 4745 | * Resolves the object `objId` with optional `data`. |
michael@0 | 4746 | */ |
michael@0 | 4747 | resolve: function PDFObjects_resolve(objId, data) { |
michael@0 | 4748 | var obj = this.ensureObj(objId); |
michael@0 | 4749 | |
michael@0 | 4750 | obj.resolved = true; |
michael@0 | 4751 | obj.data = data; |
michael@0 | 4752 | obj.promise.resolve(data); |
michael@0 | 4753 | }, |
michael@0 | 4754 | |
michael@0 | 4755 | isResolved: function PDFObjects_isResolved(objId) { |
michael@0 | 4756 | var objs = this.objs; |
michael@0 | 4757 | |
michael@0 | 4758 | if (!objs[objId]) { |
michael@0 | 4759 | return false; |
michael@0 | 4760 | } else { |
michael@0 | 4761 | return objs[objId].resolved; |
michael@0 | 4762 | } |
michael@0 | 4763 | }, |
michael@0 | 4764 | |
michael@0 | 4765 | hasData: function PDFObjects_hasData(objId) { |
michael@0 | 4766 | return this.isResolved(objId); |
michael@0 | 4767 | }, |
michael@0 | 4768 | |
michael@0 | 4769 | /** |
michael@0 | 4770 | * Returns the data of `objId` if object exists, null otherwise. |
michael@0 | 4771 | */ |
michael@0 | 4772 | getData: function PDFObjects_getData(objId) { |
michael@0 | 4773 | var objs = this.objs; |
michael@0 | 4774 | if (!objs[objId] || !objs[objId].resolved) { |
michael@0 | 4775 | return null; |
michael@0 | 4776 | } else { |
michael@0 | 4777 | return objs[objId].data; |
michael@0 | 4778 | } |
michael@0 | 4779 | }, |
michael@0 | 4780 | |
michael@0 | 4781 | clear: function PDFObjects_clear() { |
michael@0 | 4782 | this.objs = {}; |
michael@0 | 4783 | } |
michael@0 | 4784 | }; |
michael@0 | 4785 | return PDFObjects; |
michael@0 | 4786 | })(); |
michael@0 | 4787 | |
michael@0 | 4788 | /** |
michael@0 | 4789 | * Allows controlling of the rendering tasks. |
michael@0 | 4790 | * @class |
michael@0 | 4791 | */ |
michael@0 | 4792 | var RenderTask = (function RenderTaskClosure() { |
michael@0 | 4793 | function RenderTask(internalRenderTask) { |
michael@0 | 4794 | this.internalRenderTask = internalRenderTask; |
michael@0 | 4795 | /** |
michael@0 | 4796 | * Promise for rendering task completion. |
michael@0 | 4797 | * @type {Promise} |
michael@0 | 4798 | */ |
michael@0 | 4799 | this.promise = new PDFJS.LegacyPromise(); |
michael@0 | 4800 | } |
michael@0 | 4801 | |
michael@0 | 4802 | RenderTask.prototype = /** @lends RenderTask.prototype */ { |
michael@0 | 4803 | /** |
michael@0 | 4804 | * Cancels the rendering task. If the task is currently rendering it will |
michael@0 | 4805 | * not be cancelled until graphics pauses with a timeout. The promise that |
michael@0 | 4806 | * this object extends will resolved when cancelled. |
michael@0 | 4807 | */ |
michael@0 | 4808 | cancel: function RenderTask_cancel() { |
michael@0 | 4809 | this.internalRenderTask.cancel(); |
michael@0 | 4810 | this.promise.reject(new Error('Rendering is cancelled')); |
michael@0 | 4811 | }, |
michael@0 | 4812 | |
michael@0 | 4813 | /** |
michael@0 | 4814 | * Registers callback to indicate the rendering task completion. |
michael@0 | 4815 | * |
michael@0 | 4816 | * @param {function} onFulfilled The callback for the rendering completion. |
michael@0 | 4817 | * @param {function} onRejected The callback for the rendering failure. |
michael@0 | 4818 | * @return {Promise} A promise that is resolved after the onFulfilled or |
michael@0 | 4819 | * onRejected callback. |
michael@0 | 4820 | */ |
michael@0 | 4821 | then: function RenderTask_then(onFulfilled, onRejected) { |
michael@0 | 4822 | return this.promise.then(onFulfilled, onRejected); |
michael@0 | 4823 | } |
michael@0 | 4824 | }; |
michael@0 | 4825 | |
michael@0 | 4826 | return RenderTask; |
michael@0 | 4827 | })(); |
michael@0 | 4828 | |
michael@0 | 4829 | /** |
michael@0 | 4830 | * For internal use only. |
michael@0 | 4831 | * @ignore |
michael@0 | 4832 | */ |
michael@0 | 4833 | var InternalRenderTask = (function InternalRenderTaskClosure() { |
michael@0 | 4834 | |
michael@0 | 4835 | function InternalRenderTask(callback, params, objs, commonObjs, operatorList, |
michael@0 | 4836 | pageNumber) { |
michael@0 | 4837 | this.callback = callback; |
michael@0 | 4838 | this.params = params; |
michael@0 | 4839 | this.objs = objs; |
michael@0 | 4840 | this.commonObjs = commonObjs; |
michael@0 | 4841 | this.operatorListIdx = null; |
michael@0 | 4842 | this.operatorList = operatorList; |
michael@0 | 4843 | this.pageNumber = pageNumber; |
michael@0 | 4844 | this.running = false; |
michael@0 | 4845 | this.graphicsReadyCallback = null; |
michael@0 | 4846 | this.graphicsReady = false; |
michael@0 | 4847 | this.cancelled = false; |
michael@0 | 4848 | } |
michael@0 | 4849 | |
michael@0 | 4850 | InternalRenderTask.prototype = { |
michael@0 | 4851 | |
michael@0 | 4852 | initalizeGraphics: |
michael@0 | 4853 | function InternalRenderTask_initalizeGraphics(transparency) { |
michael@0 | 4854 | |
michael@0 | 4855 | if (this.cancelled) { |
michael@0 | 4856 | return; |
michael@0 | 4857 | } |
michael@0 | 4858 | if (PDFJS.pdfBug && 'StepperManager' in globalScope && |
michael@0 | 4859 | globalScope.StepperManager.enabled) { |
michael@0 | 4860 | this.stepper = globalScope.StepperManager.create(this.pageNumber - 1); |
michael@0 | 4861 | this.stepper.init(this.operatorList); |
michael@0 | 4862 | this.stepper.nextBreakPoint = this.stepper.getNextBreakPoint(); |
michael@0 | 4863 | } |
michael@0 | 4864 | |
michael@0 | 4865 | var params = this.params; |
michael@0 | 4866 | this.gfx = new CanvasGraphics(params.canvasContext, this.commonObjs, |
michael@0 | 4867 | this.objs, params.imageLayer); |
michael@0 | 4868 | |
michael@0 | 4869 | this.gfx.beginDrawing(params.viewport, transparency); |
michael@0 | 4870 | this.operatorListIdx = 0; |
michael@0 | 4871 | this.graphicsReady = true; |
michael@0 | 4872 | if (this.graphicsReadyCallback) { |
michael@0 | 4873 | this.graphicsReadyCallback(); |
michael@0 | 4874 | } |
michael@0 | 4875 | }, |
michael@0 | 4876 | |
michael@0 | 4877 | cancel: function InternalRenderTask_cancel() { |
michael@0 | 4878 | this.running = false; |
michael@0 | 4879 | this.cancelled = true; |
michael@0 | 4880 | this.callback('cancelled'); |
michael@0 | 4881 | }, |
michael@0 | 4882 | |
michael@0 | 4883 | operatorListChanged: function InternalRenderTask_operatorListChanged() { |
michael@0 | 4884 | if (!this.graphicsReady) { |
michael@0 | 4885 | if (!this.graphicsReadyCallback) { |
michael@0 | 4886 | this.graphicsReadyCallback = this._continue.bind(this); |
michael@0 | 4887 | } |
michael@0 | 4888 | return; |
michael@0 | 4889 | } |
michael@0 | 4890 | |
michael@0 | 4891 | if (this.stepper) { |
michael@0 | 4892 | this.stepper.updateOperatorList(this.operatorList); |
michael@0 | 4893 | } |
michael@0 | 4894 | |
michael@0 | 4895 | if (this.running) { |
michael@0 | 4896 | return; |
michael@0 | 4897 | } |
michael@0 | 4898 | this._continue(); |
michael@0 | 4899 | }, |
michael@0 | 4900 | |
michael@0 | 4901 | _continue: function InternalRenderTask__continue() { |
michael@0 | 4902 | this.running = true; |
michael@0 | 4903 | if (this.cancelled) { |
michael@0 | 4904 | return; |
michael@0 | 4905 | } |
michael@0 | 4906 | if (this.params.continueCallback) { |
michael@0 | 4907 | this.params.continueCallback(this._next.bind(this)); |
michael@0 | 4908 | } else { |
michael@0 | 4909 | this._next(); |
michael@0 | 4910 | } |
michael@0 | 4911 | }, |
michael@0 | 4912 | |
michael@0 | 4913 | _next: function InternalRenderTask__next() { |
michael@0 | 4914 | if (this.cancelled) { |
michael@0 | 4915 | return; |
michael@0 | 4916 | } |
michael@0 | 4917 | this.operatorListIdx = this.gfx.executeOperatorList(this.operatorList, |
michael@0 | 4918 | this.operatorListIdx, |
michael@0 | 4919 | this._continue.bind(this), |
michael@0 | 4920 | this.stepper); |
michael@0 | 4921 | if (this.operatorListIdx === this.operatorList.argsArray.length) { |
michael@0 | 4922 | this.running = false; |
michael@0 | 4923 | if (this.operatorList.lastChunk) { |
michael@0 | 4924 | this.gfx.endDrawing(); |
michael@0 | 4925 | this.callback(); |
michael@0 | 4926 | } |
michael@0 | 4927 | } |
michael@0 | 4928 | } |
michael@0 | 4929 | |
michael@0 | 4930 | }; |
michael@0 | 4931 | |
michael@0 | 4932 | return InternalRenderTask; |
michael@0 | 4933 | })(); |
michael@0 | 4934 | |
michael@0 | 4935 | |
michael@0 | 4936 | var Metadata = PDFJS.Metadata = (function MetadataClosure() { |
michael@0 | 4937 | function fixMetadata(meta) { |
michael@0 | 4938 | return meta.replace(/>\\376\\377([^<]+)/g, function(all, codes) { |
michael@0 | 4939 | var bytes = codes.replace(/\\([0-3])([0-7])([0-7])/g, |
michael@0 | 4940 | function(code, d1, d2, d3) { |
michael@0 | 4941 | return String.fromCharCode(d1 * 64 + d2 * 8 + d3 * 1); |
michael@0 | 4942 | }); |
michael@0 | 4943 | var chars = ''; |
michael@0 | 4944 | for (var i = 0; i < bytes.length; i += 2) { |
michael@0 | 4945 | var code = bytes.charCodeAt(i) * 256 + bytes.charCodeAt(i + 1); |
michael@0 | 4946 | chars += code >= 32 && code < 127 && code != 60 && code != 62 && |
michael@0 | 4947 | code != 38 && false ? String.fromCharCode(code) : |
michael@0 | 4948 | '&#x' + (0x10000 + code).toString(16).substring(1) + ';'; |
michael@0 | 4949 | } |
michael@0 | 4950 | return '>' + chars; |
michael@0 | 4951 | }); |
michael@0 | 4952 | } |
michael@0 | 4953 | |
michael@0 | 4954 | function Metadata(meta) { |
michael@0 | 4955 | if (typeof meta === 'string') { |
michael@0 | 4956 | // Ghostscript produces invalid metadata |
michael@0 | 4957 | meta = fixMetadata(meta); |
michael@0 | 4958 | |
michael@0 | 4959 | var parser = new DOMParser(); |
michael@0 | 4960 | meta = parser.parseFromString(meta, 'application/xml'); |
michael@0 | 4961 | } else if (!(meta instanceof Document)) { |
michael@0 | 4962 | error('Metadata: Invalid metadata object'); |
michael@0 | 4963 | } |
michael@0 | 4964 | |
michael@0 | 4965 | this.metaDocument = meta; |
michael@0 | 4966 | this.metadata = {}; |
michael@0 | 4967 | this.parse(); |
michael@0 | 4968 | } |
michael@0 | 4969 | |
michael@0 | 4970 | Metadata.prototype = { |
michael@0 | 4971 | parse: function Metadata_parse() { |
michael@0 | 4972 | var doc = this.metaDocument; |
michael@0 | 4973 | var rdf = doc.documentElement; |
michael@0 | 4974 | |
michael@0 | 4975 | if (rdf.nodeName.toLowerCase() !== 'rdf:rdf') { // Wrapped in <xmpmeta> |
michael@0 | 4976 | rdf = rdf.firstChild; |
michael@0 | 4977 | while (rdf && rdf.nodeName.toLowerCase() !== 'rdf:rdf') { |
michael@0 | 4978 | rdf = rdf.nextSibling; |
michael@0 | 4979 | } |
michael@0 | 4980 | } |
michael@0 | 4981 | |
michael@0 | 4982 | var nodeName = (rdf) ? rdf.nodeName.toLowerCase() : null; |
michael@0 | 4983 | if (!rdf || nodeName !== 'rdf:rdf' || !rdf.hasChildNodes()) { |
michael@0 | 4984 | return; |
michael@0 | 4985 | } |
michael@0 | 4986 | |
michael@0 | 4987 | var children = rdf.childNodes, desc, entry, name, i, ii, length, iLength; |
michael@0 | 4988 | for (i = 0, length = children.length; i < length; i++) { |
michael@0 | 4989 | desc = children[i]; |
michael@0 | 4990 | if (desc.nodeName.toLowerCase() !== 'rdf:description') { |
michael@0 | 4991 | continue; |
michael@0 | 4992 | } |
michael@0 | 4993 | |
michael@0 | 4994 | for (ii = 0, iLength = desc.childNodes.length; ii < iLength; ii++) { |
michael@0 | 4995 | if (desc.childNodes[ii].nodeName.toLowerCase() !== '#text') { |
michael@0 | 4996 | entry = desc.childNodes[ii]; |
michael@0 | 4997 | name = entry.nodeName.toLowerCase(); |
michael@0 | 4998 | this.metadata[name] = entry.textContent.trim(); |
michael@0 | 4999 | } |
michael@0 | 5000 | } |
michael@0 | 5001 | } |
michael@0 | 5002 | }, |
michael@0 | 5003 | |
michael@0 | 5004 | get: function Metadata_get(name) { |
michael@0 | 5005 | return this.metadata[name] || null; |
michael@0 | 5006 | }, |
michael@0 | 5007 | |
michael@0 | 5008 | has: function Metadata_has(name) { |
michael@0 | 5009 | return typeof this.metadata[name] !== 'undefined'; |
michael@0 | 5010 | } |
michael@0 | 5011 | }; |
michael@0 | 5012 | |
michael@0 | 5013 | return Metadata; |
michael@0 | 5014 | })(); |
michael@0 | 5015 | |
michael@0 | 5016 | |
michael@0 | 5017 | // <canvas> contexts store most of the state we need natively. |
michael@0 | 5018 | // However, PDF needs a bit more state, which we store here. |
michael@0 | 5019 | |
michael@0 | 5020 | // Minimal font size that would be used during canvas fillText operations. |
michael@0 | 5021 | var MIN_FONT_SIZE = 16; |
michael@0 | 5022 | var MAX_GROUP_SIZE = 4096; |
michael@0 | 5023 | |
michael@0 | 5024 | var COMPILE_TYPE3_GLYPHS = true; |
michael@0 | 5025 | |
michael@0 | 5026 | function createScratchCanvas(width, height) { |
michael@0 | 5027 | var canvas = document.createElement('canvas'); |
michael@0 | 5028 | canvas.width = width; |
michael@0 | 5029 | canvas.height = height; |
michael@0 | 5030 | return canvas; |
michael@0 | 5031 | } |
michael@0 | 5032 | |
michael@0 | 5033 | function addContextCurrentTransform(ctx) { |
michael@0 | 5034 | // If the context doesn't expose a `mozCurrentTransform`, add a JS based on. |
michael@0 | 5035 | if (!ctx.mozCurrentTransform) { |
michael@0 | 5036 | // Store the original context |
michael@0 | 5037 | ctx._scaleX = ctx._scaleX || 1.0; |
michael@0 | 5038 | ctx._scaleY = ctx._scaleY || 1.0; |
michael@0 | 5039 | ctx._originalSave = ctx.save; |
michael@0 | 5040 | ctx._originalRestore = ctx.restore; |
michael@0 | 5041 | ctx._originalRotate = ctx.rotate; |
michael@0 | 5042 | ctx._originalScale = ctx.scale; |
michael@0 | 5043 | ctx._originalTranslate = ctx.translate; |
michael@0 | 5044 | ctx._originalTransform = ctx.transform; |
michael@0 | 5045 | ctx._originalSetTransform = ctx.setTransform; |
michael@0 | 5046 | |
michael@0 | 5047 | ctx._transformMatrix = [ctx._scaleX, 0, 0, ctx._scaleY, 0, 0]; |
michael@0 | 5048 | ctx._transformStack = []; |
michael@0 | 5049 | |
michael@0 | 5050 | Object.defineProperty(ctx, 'mozCurrentTransform', { |
michael@0 | 5051 | get: function getCurrentTransform() { |
michael@0 | 5052 | return this._transformMatrix; |
michael@0 | 5053 | } |
michael@0 | 5054 | }); |
michael@0 | 5055 | |
michael@0 | 5056 | Object.defineProperty(ctx, 'mozCurrentTransformInverse', { |
michael@0 | 5057 | get: function getCurrentTransformInverse() { |
michael@0 | 5058 | // Calculation done using WolframAlpha: |
michael@0 | 5059 | // http://www.wolframalpha.com/input/? |
michael@0 | 5060 | // i=Inverse+{{a%2C+c%2C+e}%2C+{b%2C+d%2C+f}%2C+{0%2C+0%2C+1}} |
michael@0 | 5061 | |
michael@0 | 5062 | var m = this._transformMatrix; |
michael@0 | 5063 | var a = m[0], b = m[1], c = m[2], d = m[3], e = m[4], f = m[5]; |
michael@0 | 5064 | |
michael@0 | 5065 | var ad_bc = a * d - b * c; |
michael@0 | 5066 | var bc_ad = b * c - a * d; |
michael@0 | 5067 | |
michael@0 | 5068 | return [ |
michael@0 | 5069 | d / ad_bc, |
michael@0 | 5070 | b / bc_ad, |
michael@0 | 5071 | c / bc_ad, |
michael@0 | 5072 | a / ad_bc, |
michael@0 | 5073 | (d * e - c * f) / bc_ad, |
michael@0 | 5074 | (b * e - a * f) / ad_bc |
michael@0 | 5075 | ]; |
michael@0 | 5076 | } |
michael@0 | 5077 | }); |
michael@0 | 5078 | |
michael@0 | 5079 | ctx.save = function ctxSave() { |
michael@0 | 5080 | var old = this._transformMatrix; |
michael@0 | 5081 | this._transformStack.push(old); |
michael@0 | 5082 | this._transformMatrix = old.slice(0, 6); |
michael@0 | 5083 | |
michael@0 | 5084 | this._originalSave(); |
michael@0 | 5085 | }; |
michael@0 | 5086 | |
michael@0 | 5087 | ctx.restore = function ctxRestore() { |
michael@0 | 5088 | var prev = this._transformStack.pop(); |
michael@0 | 5089 | if (prev) { |
michael@0 | 5090 | this._transformMatrix = prev; |
michael@0 | 5091 | this._originalRestore(); |
michael@0 | 5092 | } |
michael@0 | 5093 | }; |
michael@0 | 5094 | |
michael@0 | 5095 | ctx.translate = function ctxTranslate(x, y) { |
michael@0 | 5096 | var m = this._transformMatrix; |
michael@0 | 5097 | m[4] = m[0] * x + m[2] * y + m[4]; |
michael@0 | 5098 | m[5] = m[1] * x + m[3] * y + m[5]; |
michael@0 | 5099 | |
michael@0 | 5100 | this._originalTranslate(x, y); |
michael@0 | 5101 | }; |
michael@0 | 5102 | |
michael@0 | 5103 | ctx.scale = function ctxScale(x, y) { |
michael@0 | 5104 | var m = this._transformMatrix; |
michael@0 | 5105 | m[0] = m[0] * x; |
michael@0 | 5106 | m[1] = m[1] * x; |
michael@0 | 5107 | m[2] = m[2] * y; |
michael@0 | 5108 | m[3] = m[3] * y; |
michael@0 | 5109 | |
michael@0 | 5110 | this._originalScale(x, y); |
michael@0 | 5111 | }; |
michael@0 | 5112 | |
michael@0 | 5113 | ctx.transform = function ctxTransform(a, b, c, d, e, f) { |
michael@0 | 5114 | var m = this._transformMatrix; |
michael@0 | 5115 | this._transformMatrix = [ |
michael@0 | 5116 | m[0] * a + m[2] * b, |
michael@0 | 5117 | m[1] * a + m[3] * b, |
michael@0 | 5118 | m[0] * c + m[2] * d, |
michael@0 | 5119 | m[1] * c + m[3] * d, |
michael@0 | 5120 | m[0] * e + m[2] * f + m[4], |
michael@0 | 5121 | m[1] * e + m[3] * f + m[5] |
michael@0 | 5122 | ]; |
michael@0 | 5123 | |
michael@0 | 5124 | ctx._originalTransform(a, b, c, d, e, f); |
michael@0 | 5125 | }; |
michael@0 | 5126 | |
michael@0 | 5127 | ctx.setTransform = function ctxSetTransform(a, b, c, d, e, f) { |
michael@0 | 5128 | this._transformMatrix = [a, b, c, d, e, f]; |
michael@0 | 5129 | |
michael@0 | 5130 | ctx._originalSetTransform(a, b, c, d, e, f); |
michael@0 | 5131 | }; |
michael@0 | 5132 | |
michael@0 | 5133 | ctx.rotate = function ctxRotate(angle) { |
michael@0 | 5134 | var cosValue = Math.cos(angle); |
michael@0 | 5135 | var sinValue = Math.sin(angle); |
michael@0 | 5136 | |
michael@0 | 5137 | var m = this._transformMatrix; |
michael@0 | 5138 | this._transformMatrix = [ |
michael@0 | 5139 | m[0] * cosValue + m[2] * sinValue, |
michael@0 | 5140 | m[1] * cosValue + m[3] * sinValue, |
michael@0 | 5141 | m[0] * (-sinValue) + m[2] * cosValue, |
michael@0 | 5142 | m[1] * (-sinValue) + m[3] * cosValue, |
michael@0 | 5143 | m[4], |
michael@0 | 5144 | m[5] |
michael@0 | 5145 | ]; |
michael@0 | 5146 | |
michael@0 | 5147 | this._originalRotate(angle); |
michael@0 | 5148 | }; |
michael@0 | 5149 | } |
michael@0 | 5150 | } |
michael@0 | 5151 | |
michael@0 | 5152 | var CachedCanvases = (function CachedCanvasesClosure() { |
michael@0 | 5153 | var cache = {}; |
michael@0 | 5154 | return { |
michael@0 | 5155 | getCanvas: function CachedCanvases_getCanvas(id, width, height, |
michael@0 | 5156 | trackTransform) { |
michael@0 | 5157 | var canvasEntry; |
michael@0 | 5158 | if (id in cache) { |
michael@0 | 5159 | canvasEntry = cache[id]; |
michael@0 | 5160 | canvasEntry.canvas.width = width; |
michael@0 | 5161 | canvasEntry.canvas.height = height; |
michael@0 | 5162 | // reset canvas transform for emulated mozCurrentTransform, if needed |
michael@0 | 5163 | canvasEntry.context.setTransform(1, 0, 0, 1, 0, 0); |
michael@0 | 5164 | } else { |
michael@0 | 5165 | var canvas = createScratchCanvas(width, height); |
michael@0 | 5166 | var ctx = canvas.getContext('2d'); |
michael@0 | 5167 | if (trackTransform) { |
michael@0 | 5168 | addContextCurrentTransform(ctx); |
michael@0 | 5169 | } |
michael@0 | 5170 | cache[id] = canvasEntry = {canvas: canvas, context: ctx}; |
michael@0 | 5171 | } |
michael@0 | 5172 | return canvasEntry; |
michael@0 | 5173 | }, |
michael@0 | 5174 | clear: function () { |
michael@0 | 5175 | cache = {}; |
michael@0 | 5176 | } |
michael@0 | 5177 | }; |
michael@0 | 5178 | })(); |
michael@0 | 5179 | |
michael@0 | 5180 | function compileType3Glyph(imgData) { |
michael@0 | 5181 | var POINT_TO_PROCESS_LIMIT = 1000; |
michael@0 | 5182 | |
michael@0 | 5183 | var width = imgData.width, height = imgData.height; |
michael@0 | 5184 | var i, j, j0, width1 = width + 1; |
michael@0 | 5185 | var points = new Uint8Array(width1 * (height + 1)); |
michael@0 | 5186 | var POINT_TYPES = |
michael@0 | 5187 | new Uint8Array([0, 2, 4, 0, 1, 0, 5, 4, 8, 10, 0, 8, 0, 2, 1, 0]); |
michael@0 | 5188 | |
michael@0 | 5189 | // decodes bit-packed mask data |
michael@0 | 5190 | var lineSize = (width + 7) & ~7, data0 = imgData.data; |
michael@0 | 5191 | var data = new Uint8Array(lineSize * height), pos = 0, ii; |
michael@0 | 5192 | for (i = 0, ii = data0.length; i < ii; i++) { |
michael@0 | 5193 | var mask = 128, elem = data0[i]; |
michael@0 | 5194 | while (mask > 0) { |
michael@0 | 5195 | data[pos++] = (elem & mask) ? 0 : 255; |
michael@0 | 5196 | mask >>= 1; |
michael@0 | 5197 | } |
michael@0 | 5198 | } |
michael@0 | 5199 | |
michael@0 | 5200 | // finding iteresting points: every point is located between mask pixels, |
michael@0 | 5201 | // so there will be points of the (width + 1)x(height + 1) grid. Every point |
michael@0 | 5202 | // will have flags assigned based on neighboring mask pixels: |
michael@0 | 5203 | // 4 | 8 |
michael@0 | 5204 | // --P-- |
michael@0 | 5205 | // 2 | 1 |
michael@0 | 5206 | // We are interested only in points with the flags: |
michael@0 | 5207 | // - outside corners: 1, 2, 4, 8; |
michael@0 | 5208 | // - inside corners: 7, 11, 13, 14; |
michael@0 | 5209 | // - and, intersections: 5, 10. |
michael@0 | 5210 | var count = 0; |
michael@0 | 5211 | pos = 0; |
michael@0 | 5212 | if (data[pos] !== 0) { |
michael@0 | 5213 | points[0] = 1; |
michael@0 | 5214 | ++count; |
michael@0 | 5215 | } |
michael@0 | 5216 | for (j = 1; j < width; j++) { |
michael@0 | 5217 | if (data[pos] !== data[pos + 1]) { |
michael@0 | 5218 | points[j] = data[pos] ? 2 : 1; |
michael@0 | 5219 | ++count; |
michael@0 | 5220 | } |
michael@0 | 5221 | pos++; |
michael@0 | 5222 | } |
michael@0 | 5223 | if (data[pos] !== 0) { |
michael@0 | 5224 | points[j] = 2; |
michael@0 | 5225 | ++count; |
michael@0 | 5226 | } |
michael@0 | 5227 | for (i = 1; i < height; i++) { |
michael@0 | 5228 | pos = i * lineSize; |
michael@0 | 5229 | j0 = i * width1; |
michael@0 | 5230 | if (data[pos - lineSize] !== data[pos]) { |
michael@0 | 5231 | points[j0] = data[pos] ? 1 : 8; |
michael@0 | 5232 | ++count; |
michael@0 | 5233 | } |
michael@0 | 5234 | // 'sum' is the position of the current pixel configuration in the 'TYPES' |
michael@0 | 5235 | // array (in order 8-1-2-4, so we can use '>>2' to shift the column). |
michael@0 | 5236 | var sum = (data[pos] ? 4 : 0) + (data[pos - lineSize] ? 8 : 0); |
michael@0 | 5237 | for (j = 1; j < width; j++) { |
michael@0 | 5238 | sum = (sum >> 2) + (data[pos + 1] ? 4 : 0) + |
michael@0 | 5239 | (data[pos - lineSize + 1] ? 8 : 0); |
michael@0 | 5240 | if (POINT_TYPES[sum]) { |
michael@0 | 5241 | points[j0 + j] = POINT_TYPES[sum]; |
michael@0 | 5242 | ++count; |
michael@0 | 5243 | } |
michael@0 | 5244 | pos++; |
michael@0 | 5245 | } |
michael@0 | 5246 | if (data[pos - lineSize] !== data[pos]) { |
michael@0 | 5247 | points[j0 + j] = data[pos] ? 2 : 4; |
michael@0 | 5248 | ++count; |
michael@0 | 5249 | } |
michael@0 | 5250 | |
michael@0 | 5251 | if (count > POINT_TO_PROCESS_LIMIT) { |
michael@0 | 5252 | return null; |
michael@0 | 5253 | } |
michael@0 | 5254 | } |
michael@0 | 5255 | |
michael@0 | 5256 | pos = lineSize * (height - 1); |
michael@0 | 5257 | j0 = i * width1; |
michael@0 | 5258 | if (data[pos] !== 0) { |
michael@0 | 5259 | points[j0] = 8; |
michael@0 | 5260 | ++count; |
michael@0 | 5261 | } |
michael@0 | 5262 | for (j = 1; j < width; j++) { |
michael@0 | 5263 | if (data[pos] !== data[pos + 1]) { |
michael@0 | 5264 | points[j0 + j] = data[pos] ? 4 : 8; |
michael@0 | 5265 | ++count; |
michael@0 | 5266 | } |
michael@0 | 5267 | pos++; |
michael@0 | 5268 | } |
michael@0 | 5269 | if (data[pos] !== 0) { |
michael@0 | 5270 | points[j0 + j] = 4; |
michael@0 | 5271 | ++count; |
michael@0 | 5272 | } |
michael@0 | 5273 | if (count > POINT_TO_PROCESS_LIMIT) { |
michael@0 | 5274 | return null; |
michael@0 | 5275 | } |
michael@0 | 5276 | |
michael@0 | 5277 | // building outlines |
michael@0 | 5278 | var steps = new Int32Array([0, width1, -1, 0, -width1, 0, 0, 0, 1]); |
michael@0 | 5279 | var outlines = []; |
michael@0 | 5280 | for (i = 0; count && i <= height; i++) { |
michael@0 | 5281 | var p = i * width1; |
michael@0 | 5282 | var end = p + width; |
michael@0 | 5283 | while (p < end && !points[p]) { |
michael@0 | 5284 | p++; |
michael@0 | 5285 | } |
michael@0 | 5286 | if (p === end) { |
michael@0 | 5287 | continue; |
michael@0 | 5288 | } |
michael@0 | 5289 | var coords = [p % width1, i]; |
michael@0 | 5290 | |
michael@0 | 5291 | var type = points[p], p0 = p, pp; |
michael@0 | 5292 | do { |
michael@0 | 5293 | var step = steps[type]; |
michael@0 | 5294 | do { |
michael@0 | 5295 | p += step; |
michael@0 | 5296 | } while (!points[p]); |
michael@0 | 5297 | |
michael@0 | 5298 | pp = points[p]; |
michael@0 | 5299 | if (pp !== 5 && pp !== 10) { |
michael@0 | 5300 | // set new direction |
michael@0 | 5301 | type = pp; |
michael@0 | 5302 | // delete mark |
michael@0 | 5303 | points[p] = 0; |
michael@0 | 5304 | } else { // type is 5 or 10, ie, a crossing |
michael@0 | 5305 | // set new direction |
michael@0 | 5306 | type = pp & ((0x33 * type) >> 4); |
michael@0 | 5307 | // set new type for "future hit" |
michael@0 | 5308 | points[p] &= (type >> 2 | type << 2); |
michael@0 | 5309 | } |
michael@0 | 5310 | |
michael@0 | 5311 | coords.push(p % width1); |
michael@0 | 5312 | coords.push((p / width1) | 0); |
michael@0 | 5313 | --count; |
michael@0 | 5314 | } while (p0 !== p); |
michael@0 | 5315 | outlines.push(coords); |
michael@0 | 5316 | --i; |
michael@0 | 5317 | } |
michael@0 | 5318 | |
michael@0 | 5319 | var drawOutline = function(c) { |
michael@0 | 5320 | c.save(); |
michael@0 | 5321 | // the path shall be painted in [0..1]x[0..1] space |
michael@0 | 5322 | c.scale(1 / width, -1 / height); |
michael@0 | 5323 | c.translate(0, -height); |
michael@0 | 5324 | c.beginPath(); |
michael@0 | 5325 | for (var i = 0, ii = outlines.length; i < ii; i++) { |
michael@0 | 5326 | var o = outlines[i]; |
michael@0 | 5327 | c.moveTo(o[0], o[1]); |
michael@0 | 5328 | for (var j = 2, jj = o.length; j < jj; j += 2) { |
michael@0 | 5329 | c.lineTo(o[j], o[j+1]); |
michael@0 | 5330 | } |
michael@0 | 5331 | } |
michael@0 | 5332 | c.fill(); |
michael@0 | 5333 | c.beginPath(); |
michael@0 | 5334 | c.restore(); |
michael@0 | 5335 | }; |
michael@0 | 5336 | |
michael@0 | 5337 | return drawOutline; |
michael@0 | 5338 | } |
michael@0 | 5339 | |
michael@0 | 5340 | var CanvasExtraState = (function CanvasExtraStateClosure() { |
michael@0 | 5341 | function CanvasExtraState(old) { |
michael@0 | 5342 | // Are soft masks and alpha values shapes or opacities? |
michael@0 | 5343 | this.alphaIsShape = false; |
michael@0 | 5344 | this.fontSize = 0; |
michael@0 | 5345 | this.fontSizeScale = 1; |
michael@0 | 5346 | this.textMatrix = IDENTITY_MATRIX; |
michael@0 | 5347 | this.fontMatrix = FONT_IDENTITY_MATRIX; |
michael@0 | 5348 | this.leading = 0; |
michael@0 | 5349 | // Current point (in user coordinates) |
michael@0 | 5350 | this.x = 0; |
michael@0 | 5351 | this.y = 0; |
michael@0 | 5352 | // Start of text line (in text coordinates) |
michael@0 | 5353 | this.lineX = 0; |
michael@0 | 5354 | this.lineY = 0; |
michael@0 | 5355 | // Character and word spacing |
michael@0 | 5356 | this.charSpacing = 0; |
michael@0 | 5357 | this.wordSpacing = 0; |
michael@0 | 5358 | this.textHScale = 1; |
michael@0 | 5359 | this.textRenderingMode = TextRenderingMode.FILL; |
michael@0 | 5360 | this.textRise = 0; |
michael@0 | 5361 | // Color spaces |
michael@0 | 5362 | this.fillColorSpace = ColorSpace.singletons.gray; |
michael@0 | 5363 | this.fillColorSpaceObj = null; |
michael@0 | 5364 | this.strokeColorSpace = ColorSpace.singletons.gray; |
michael@0 | 5365 | this.strokeColorSpaceObj = null; |
michael@0 | 5366 | this.fillColorObj = null; |
michael@0 | 5367 | this.strokeColorObj = null; |
michael@0 | 5368 | // Default fore and background colors |
michael@0 | 5369 | this.fillColor = '#000000'; |
michael@0 | 5370 | this.strokeColor = '#000000'; |
michael@0 | 5371 | // Note: fill alpha applies to all non-stroking operations |
michael@0 | 5372 | this.fillAlpha = 1; |
michael@0 | 5373 | this.strokeAlpha = 1; |
michael@0 | 5374 | this.lineWidth = 1; |
michael@0 | 5375 | this.activeSMask = null; // nonclonable field (see the save method below) |
michael@0 | 5376 | |
michael@0 | 5377 | this.old = old; |
michael@0 | 5378 | } |
michael@0 | 5379 | |
michael@0 | 5380 | CanvasExtraState.prototype = { |
michael@0 | 5381 | clone: function CanvasExtraState_clone() { |
michael@0 | 5382 | return Object.create(this); |
michael@0 | 5383 | }, |
michael@0 | 5384 | setCurrentPoint: function CanvasExtraState_setCurrentPoint(x, y) { |
michael@0 | 5385 | this.x = x; |
michael@0 | 5386 | this.y = y; |
michael@0 | 5387 | } |
michael@0 | 5388 | }; |
michael@0 | 5389 | return CanvasExtraState; |
michael@0 | 5390 | })(); |
michael@0 | 5391 | |
michael@0 | 5392 | var CanvasGraphics = (function CanvasGraphicsClosure() { |
michael@0 | 5393 | // Defines the time the executeOperatorList is going to be executing |
michael@0 | 5394 | // before it stops and shedules a continue of execution. |
michael@0 | 5395 | var EXECUTION_TIME = 15; |
michael@0 | 5396 | |
michael@0 | 5397 | function CanvasGraphics(canvasCtx, commonObjs, objs, imageLayer) { |
michael@0 | 5398 | this.ctx = canvasCtx; |
michael@0 | 5399 | this.current = new CanvasExtraState(); |
michael@0 | 5400 | this.stateStack = []; |
michael@0 | 5401 | this.pendingClip = null; |
michael@0 | 5402 | this.pendingEOFill = false; |
michael@0 | 5403 | this.res = null; |
michael@0 | 5404 | this.xobjs = null; |
michael@0 | 5405 | this.commonObjs = commonObjs; |
michael@0 | 5406 | this.objs = objs; |
michael@0 | 5407 | this.imageLayer = imageLayer; |
michael@0 | 5408 | this.groupStack = []; |
michael@0 | 5409 | this.processingType3 = null; |
michael@0 | 5410 | // Patterns are painted relative to the initial page/form transform, see pdf |
michael@0 | 5411 | // spec 8.7.2 NOTE 1. |
michael@0 | 5412 | this.baseTransform = null; |
michael@0 | 5413 | this.baseTransformStack = []; |
michael@0 | 5414 | this.groupLevel = 0; |
michael@0 | 5415 | this.smaskStack = []; |
michael@0 | 5416 | this.smaskCounter = 0; |
michael@0 | 5417 | this.tempSMask = null; |
michael@0 | 5418 | if (canvasCtx) { |
michael@0 | 5419 | addContextCurrentTransform(canvasCtx); |
michael@0 | 5420 | } |
michael@0 | 5421 | } |
michael@0 | 5422 | |
michael@0 | 5423 | function putBinaryImageData(ctx, imgData) { |
michael@0 | 5424 | if (typeof ImageData !== 'undefined' && imgData instanceof ImageData) { |
michael@0 | 5425 | ctx.putImageData(imgData, 0, 0); |
michael@0 | 5426 | return; |
michael@0 | 5427 | } |
michael@0 | 5428 | |
michael@0 | 5429 | // Put the image data to the canvas in chunks, rather than putting the |
michael@0 | 5430 | // whole image at once. This saves JS memory, because the ImageData object |
michael@0 | 5431 | // is smaller. It also possibly saves C++ memory within the implementation |
michael@0 | 5432 | // of putImageData(). (E.g. in Firefox we make two short-lived copies of |
michael@0 | 5433 | // the data passed to putImageData()). |n| shouldn't be too small, however, |
michael@0 | 5434 | // because too many putImageData() calls will slow things down. |
michael@0 | 5435 | // |
michael@0 | 5436 | // Note: as written, if the last chunk is partial, the putImageData() call |
michael@0 | 5437 | // will (conceptually) put pixels past the bounds of the canvas. But |
michael@0 | 5438 | // that's ok; any such pixels are ignored. |
michael@0 | 5439 | |
michael@0 | 5440 | var height = imgData.height, width = imgData.width; |
michael@0 | 5441 | var fullChunkHeight = 16; |
michael@0 | 5442 | var fracChunks = height / fullChunkHeight; |
michael@0 | 5443 | var fullChunks = Math.floor(fracChunks); |
michael@0 | 5444 | var totalChunks = Math.ceil(fracChunks); |
michael@0 | 5445 | var partialChunkHeight = height - fullChunks * fullChunkHeight; |
michael@0 | 5446 | |
michael@0 | 5447 | var chunkImgData = ctx.createImageData(width, fullChunkHeight); |
michael@0 | 5448 | var srcPos = 0, destPos; |
michael@0 | 5449 | var src = imgData.data; |
michael@0 | 5450 | var dest = chunkImgData.data; |
michael@0 | 5451 | var i, j, thisChunkHeight, elemsInThisChunk; |
michael@0 | 5452 | |
michael@0 | 5453 | // There are multiple forms in which the pixel data can be passed, and |
michael@0 | 5454 | // imgData.kind tells us which one this is. |
michael@0 | 5455 | if (imgData.kind === ImageKind.GRAYSCALE_1BPP) { |
michael@0 | 5456 | // Grayscale, 1 bit per pixel (i.e. black-and-white). |
michael@0 | 5457 | var srcLength = src.byteLength; |
michael@0 | 5458 | var dest32 = PDFJS.hasCanvasTypedArrays ? new Uint32Array(dest.buffer) : |
michael@0 | 5459 | new Uint32ArrayView(dest); |
michael@0 | 5460 | var dest32DataLength = dest32.length; |
michael@0 | 5461 | var fullSrcDiff = (width + 7) >> 3; |
michael@0 | 5462 | var white = 0xFFFFFFFF; |
michael@0 | 5463 | var black = (PDFJS.isLittleEndian || !PDFJS.hasCanvasTypedArrays) ? |
michael@0 | 5464 | 0xFF000000 : 0x000000FF; |
michael@0 | 5465 | for (i = 0; i < totalChunks; i++) { |
michael@0 | 5466 | thisChunkHeight = |
michael@0 | 5467 | (i < fullChunks) ? fullChunkHeight : partialChunkHeight; |
michael@0 | 5468 | destPos = 0; |
michael@0 | 5469 | for (j = 0; j < thisChunkHeight; j++) { |
michael@0 | 5470 | var srcDiff = srcLength - srcPos; |
michael@0 | 5471 | var k = 0; |
michael@0 | 5472 | var kEnd = (srcDiff > fullSrcDiff) ? width : srcDiff * 8 - 7; |
michael@0 | 5473 | var kEndUnrolled = kEnd & ~7; |
michael@0 | 5474 | var mask = 0; |
michael@0 | 5475 | var srcByte = 0; |
michael@0 | 5476 | for (; k < kEndUnrolled; k += 8) { |
michael@0 | 5477 | srcByte = src[srcPos++]; |
michael@0 | 5478 | dest32[destPos++] = (srcByte & 128) ? white : black; |
michael@0 | 5479 | dest32[destPos++] = (srcByte & 64) ? white : black; |
michael@0 | 5480 | dest32[destPos++] = (srcByte & 32) ? white : black; |
michael@0 | 5481 | dest32[destPos++] = (srcByte & 16) ? white : black; |
michael@0 | 5482 | dest32[destPos++] = (srcByte & 8) ? white : black; |
michael@0 | 5483 | dest32[destPos++] = (srcByte & 4) ? white : black; |
michael@0 | 5484 | dest32[destPos++] = (srcByte & 2) ? white : black; |
michael@0 | 5485 | dest32[destPos++] = (srcByte & 1) ? white : black; |
michael@0 | 5486 | } |
michael@0 | 5487 | for (; k < kEnd; k++) { |
michael@0 | 5488 | if (mask === 0) { |
michael@0 | 5489 | srcByte = src[srcPos++]; |
michael@0 | 5490 | mask = 128; |
michael@0 | 5491 | } |
michael@0 | 5492 | |
michael@0 | 5493 | dest32[destPos++] = (srcByte & mask) ? white : black; |
michael@0 | 5494 | mask >>= 1; |
michael@0 | 5495 | } |
michael@0 | 5496 | } |
michael@0 | 5497 | // We ran out of input. Make all remaining pixels transparent. |
michael@0 | 5498 | while (destPos < dest32DataLength) { |
michael@0 | 5499 | dest32[destPos++] = 0; |
michael@0 | 5500 | } |
michael@0 | 5501 | |
michael@0 | 5502 | ctx.putImageData(chunkImgData, 0, i * fullChunkHeight); |
michael@0 | 5503 | } |
michael@0 | 5504 | } else if (imgData.kind === ImageKind.RGBA_32BPP) { |
michael@0 | 5505 | // RGBA, 32-bits per pixel. |
michael@0 | 5506 | |
michael@0 | 5507 | for (i = 0; i < totalChunks; i++) { |
michael@0 | 5508 | thisChunkHeight = |
michael@0 | 5509 | (i < fullChunks) ? fullChunkHeight : partialChunkHeight; |
michael@0 | 5510 | elemsInThisChunk = imgData.width * thisChunkHeight * 4; |
michael@0 | 5511 | |
michael@0 | 5512 | dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk)); |
michael@0 | 5513 | srcPos += elemsInThisChunk; |
michael@0 | 5514 | |
michael@0 | 5515 | ctx.putImageData(chunkImgData, 0, i * fullChunkHeight); |
michael@0 | 5516 | } |
michael@0 | 5517 | } else if (imgData.kind === ImageKind.RGB_24BPP) { |
michael@0 | 5518 | // RGB, 24-bits per pixel. |
michael@0 | 5519 | for (i = 0; i < totalChunks; i++) { |
michael@0 | 5520 | thisChunkHeight = |
michael@0 | 5521 | (i < fullChunks) ? fullChunkHeight : partialChunkHeight; |
michael@0 | 5522 | elemsInThisChunk = imgData.width * thisChunkHeight * 3; |
michael@0 | 5523 | destPos = 0; |
michael@0 | 5524 | for (j = 0; j < elemsInThisChunk; j += 3) { |
michael@0 | 5525 | dest[destPos++] = src[srcPos++]; |
michael@0 | 5526 | dest[destPos++] = src[srcPos++]; |
michael@0 | 5527 | dest[destPos++] = src[srcPos++]; |
michael@0 | 5528 | dest[destPos++] = 255; |
michael@0 | 5529 | } |
michael@0 | 5530 | ctx.putImageData(chunkImgData, 0, i * fullChunkHeight); |
michael@0 | 5531 | } |
michael@0 | 5532 | } else { |
michael@0 | 5533 | error('bad image kind: ' + imgData.kind); |
michael@0 | 5534 | } |
michael@0 | 5535 | } |
michael@0 | 5536 | |
michael@0 | 5537 | function putBinaryImageMask(ctx, imgData) { |
michael@0 | 5538 | var height = imgData.height, width = imgData.width; |
michael@0 | 5539 | var fullChunkHeight = 16; |
michael@0 | 5540 | var fracChunks = height / fullChunkHeight; |
michael@0 | 5541 | var fullChunks = Math.floor(fracChunks); |
michael@0 | 5542 | var totalChunks = Math.ceil(fracChunks); |
michael@0 | 5543 | var partialChunkHeight = height - fullChunks * fullChunkHeight; |
michael@0 | 5544 | |
michael@0 | 5545 | var chunkImgData = ctx.createImageData(width, fullChunkHeight); |
michael@0 | 5546 | var srcPos = 0; |
michael@0 | 5547 | var src = imgData.data; |
michael@0 | 5548 | var dest = chunkImgData.data; |
michael@0 | 5549 | |
michael@0 | 5550 | for (var i = 0; i < totalChunks; i++) { |
michael@0 | 5551 | var thisChunkHeight = |
michael@0 | 5552 | (i < fullChunks) ? fullChunkHeight : partialChunkHeight; |
michael@0 | 5553 | |
michael@0 | 5554 | // Expand the mask so it can be used by the canvas. Any required |
michael@0 | 5555 | // inversion has already been handled. |
michael@0 | 5556 | var destPos = 3; // alpha component offset |
michael@0 | 5557 | for (var j = 0; j < thisChunkHeight; j++) { |
michael@0 | 5558 | var mask = 0; |
michael@0 | 5559 | for (var k = 0; k < width; k++) { |
michael@0 | 5560 | if (!mask) { |
michael@0 | 5561 | var elem = src[srcPos++]; |
michael@0 | 5562 | mask = 128; |
michael@0 | 5563 | } |
michael@0 | 5564 | dest[destPos] = (elem & mask) ? 0 : 255; |
michael@0 | 5565 | destPos += 4; |
michael@0 | 5566 | mask >>= 1; |
michael@0 | 5567 | } |
michael@0 | 5568 | } |
michael@0 | 5569 | ctx.putImageData(chunkImgData, 0, i * fullChunkHeight); |
michael@0 | 5570 | } |
michael@0 | 5571 | } |
michael@0 | 5572 | |
michael@0 | 5573 | function copyCtxState(sourceCtx, destCtx) { |
michael@0 | 5574 | var properties = ['strokeStyle', 'fillStyle', 'fillRule', 'globalAlpha', |
michael@0 | 5575 | 'lineWidth', 'lineCap', 'lineJoin', 'miterLimit', |
michael@0 | 5576 | 'globalCompositeOperation', 'font']; |
michael@0 | 5577 | for (var i = 0, ii = properties.length; i < ii; i++) { |
michael@0 | 5578 | var property = properties[i]; |
michael@0 | 5579 | if (property in sourceCtx) { |
michael@0 | 5580 | destCtx[property] = sourceCtx[property]; |
michael@0 | 5581 | } |
michael@0 | 5582 | } |
michael@0 | 5583 | if ('setLineDash' in sourceCtx) { |
michael@0 | 5584 | destCtx.setLineDash(sourceCtx.getLineDash()); |
michael@0 | 5585 | destCtx.lineDashOffset = sourceCtx.lineDashOffset; |
michael@0 | 5586 | } else if ('mozDash' in sourceCtx) { |
michael@0 | 5587 | destCtx.mozDash = sourceCtx.mozDash; |
michael@0 | 5588 | destCtx.mozDashOffset = sourceCtx.mozDashOffset; |
michael@0 | 5589 | } |
michael@0 | 5590 | } |
michael@0 | 5591 | |
michael@0 | 5592 | function genericComposeSMask(maskCtx, layerCtx, width, height, |
michael@0 | 5593 | subtype, backdrop) { |
michael@0 | 5594 | var addBackdropFn; |
michael@0 | 5595 | if (backdrop) { |
michael@0 | 5596 | addBackdropFn = function (r0, g0, b0, bytes) { |
michael@0 | 5597 | var length = bytes.length; |
michael@0 | 5598 | for (var i = 3; i < length; i += 4) { |
michael@0 | 5599 | var alpha = bytes[i] / 255; |
michael@0 | 5600 | if (alpha === 0) { |
michael@0 | 5601 | bytes[i - 3] = r0; |
michael@0 | 5602 | bytes[i - 2] = g0; |
michael@0 | 5603 | bytes[i - 1] = b0; |
michael@0 | 5604 | } else if (alpha < 1) { |
michael@0 | 5605 | var alpha_ = 1 - alpha; |
michael@0 | 5606 | bytes[i - 3] = (bytes[i - 3] * alpha + r0 * alpha_) | 0; |
michael@0 | 5607 | bytes[i - 2] = (bytes[i - 2] * alpha + g0 * alpha_) | 0; |
michael@0 | 5608 | bytes[i - 1] = (bytes[i - 1] * alpha + b0 * alpha_) | 0; |
michael@0 | 5609 | } |
michael@0 | 5610 | } |
michael@0 | 5611 | }.bind(null, backdrop[0], backdrop[1], backdrop[2]); |
michael@0 | 5612 | } else { |
michael@0 | 5613 | addBackdropFn = function () {}; |
michael@0 | 5614 | } |
michael@0 | 5615 | |
michael@0 | 5616 | var composeFn; |
michael@0 | 5617 | if (subtype === 'Luminosity') { |
michael@0 | 5618 | composeFn = function (maskDataBytes, layerDataBytes) { |
michael@0 | 5619 | var length = maskDataBytes.length; |
michael@0 | 5620 | for (var i = 3; i < length; i += 4) { |
michael@0 | 5621 | var y = ((maskDataBytes[i - 3] * 77) + // * 0.3 / 255 * 0x10000 |
michael@0 | 5622 | (maskDataBytes[i - 2] * 152) + // * 0.59 .... |
michael@0 | 5623 | (maskDataBytes[i - 1] * 28)) | 0; // * 0.11 .... |
michael@0 | 5624 | layerDataBytes[i] = (layerDataBytes[i] * y) >> 16; |
michael@0 | 5625 | } |
michael@0 | 5626 | }; |
michael@0 | 5627 | } else { |
michael@0 | 5628 | composeFn = function (maskDataBytes, layerDataBytes) { |
michael@0 | 5629 | var length = maskDataBytes.length; |
michael@0 | 5630 | for (var i = 3; i < length; i += 4) { |
michael@0 | 5631 | var alpha = maskDataBytes[i]; |
michael@0 | 5632 | layerDataBytes[i] = (layerDataBytes[i] * alpha / 255) | 0; |
michael@0 | 5633 | } |
michael@0 | 5634 | }; |
michael@0 | 5635 | } |
michael@0 | 5636 | |
michael@0 | 5637 | // processing image in chunks to save memory |
michael@0 | 5638 | var PIXELS_TO_PROCESS = 65536; |
michael@0 | 5639 | var chunkSize = Math.min(height, Math.ceil(PIXELS_TO_PROCESS / width)); |
michael@0 | 5640 | for (var row = 0; row < height; row += chunkSize) { |
michael@0 | 5641 | var chunkHeight = Math.min(chunkSize, height - row); |
michael@0 | 5642 | var maskData = maskCtx.getImageData(0, row, width, chunkHeight); |
michael@0 | 5643 | var layerData = layerCtx.getImageData(0, row, width, chunkHeight); |
michael@0 | 5644 | |
michael@0 | 5645 | addBackdropFn(maskData.data); |
michael@0 | 5646 | composeFn(maskData.data, layerData.data); |
michael@0 | 5647 | |
michael@0 | 5648 | maskCtx.putImageData(layerData, 0, row); |
michael@0 | 5649 | } |
michael@0 | 5650 | } |
michael@0 | 5651 | |
michael@0 | 5652 | function composeSMask(ctx, smask, layerCtx) { |
michael@0 | 5653 | var mask = smask.canvas; |
michael@0 | 5654 | var maskCtx = smask.context; |
michael@0 | 5655 | |
michael@0 | 5656 | ctx.setTransform(smask.scaleX, 0, 0, smask.scaleY, |
michael@0 | 5657 | smask.offsetX, smask.offsetY); |
michael@0 | 5658 | |
michael@0 | 5659 | var backdrop; |
michael@0 | 5660 | if (smask.backdrop) { |
michael@0 | 5661 | var cs = smask.colorSpace || ColorSpace.singletons.rgb; |
michael@0 | 5662 | backdrop = cs.getRgb(smask.backdrop, 0); |
michael@0 | 5663 | } |
michael@0 | 5664 | if (WebGLUtils.isEnabled) { |
michael@0 | 5665 | var composed = WebGLUtils.composeSMask(layerCtx.canvas, mask, |
michael@0 | 5666 | {subtype: smask.subtype, backdrop: backdrop}); |
michael@0 | 5667 | ctx.setTransform(1, 0, 0, 1, 0, 0); |
michael@0 | 5668 | ctx.drawImage(composed, smask.offsetX, smask.offsetY); |
michael@0 | 5669 | return; |
michael@0 | 5670 | } |
michael@0 | 5671 | genericComposeSMask(maskCtx, layerCtx, mask.width, mask.height, |
michael@0 | 5672 | smask.subtype, backdrop); |
michael@0 | 5673 | ctx.drawImage(mask, 0, 0); |
michael@0 | 5674 | } |
michael@0 | 5675 | |
michael@0 | 5676 | var LINE_CAP_STYLES = ['butt', 'round', 'square']; |
michael@0 | 5677 | var LINE_JOIN_STYLES = ['miter', 'round', 'bevel']; |
michael@0 | 5678 | var NORMAL_CLIP = {}; |
michael@0 | 5679 | var EO_CLIP = {}; |
michael@0 | 5680 | |
michael@0 | 5681 | CanvasGraphics.prototype = { |
michael@0 | 5682 | |
michael@0 | 5683 | beginDrawing: function CanvasGraphics_beginDrawing(viewport, transparency) { |
michael@0 | 5684 | // For pdfs that use blend modes we have to clear the canvas else certain |
michael@0 | 5685 | // blend modes can look wrong since we'd be blending with a white |
michael@0 | 5686 | // backdrop. The problem with a transparent backdrop though is we then |
michael@0 | 5687 | // don't get sub pixel anti aliasing on text, so we fill with white if |
michael@0 | 5688 | // we can. |
michael@0 | 5689 | var width = this.ctx.canvas.width; |
michael@0 | 5690 | var height = this.ctx.canvas.height; |
michael@0 | 5691 | if (transparency) { |
michael@0 | 5692 | this.ctx.clearRect(0, 0, width, height); |
michael@0 | 5693 | } else { |
michael@0 | 5694 | this.ctx.mozOpaque = true; |
michael@0 | 5695 | this.ctx.save(); |
michael@0 | 5696 | this.ctx.fillStyle = 'rgb(255, 255, 255)'; |
michael@0 | 5697 | this.ctx.fillRect(0, 0, width, height); |
michael@0 | 5698 | this.ctx.restore(); |
michael@0 | 5699 | } |
michael@0 | 5700 | |
michael@0 | 5701 | var transform = viewport.transform; |
michael@0 | 5702 | |
michael@0 | 5703 | this.ctx.save(); |
michael@0 | 5704 | this.ctx.transform.apply(this.ctx, transform); |
michael@0 | 5705 | |
michael@0 | 5706 | this.baseTransform = this.ctx.mozCurrentTransform.slice(); |
michael@0 | 5707 | |
michael@0 | 5708 | if (this.imageLayer) { |
michael@0 | 5709 | this.imageLayer.beginLayout(); |
michael@0 | 5710 | } |
michael@0 | 5711 | }, |
michael@0 | 5712 | |
michael@0 | 5713 | executeOperatorList: function CanvasGraphics_executeOperatorList( |
michael@0 | 5714 | operatorList, |
michael@0 | 5715 | executionStartIdx, continueCallback, |
michael@0 | 5716 | stepper) { |
michael@0 | 5717 | var argsArray = operatorList.argsArray; |
michael@0 | 5718 | var fnArray = operatorList.fnArray; |
michael@0 | 5719 | var i = executionStartIdx || 0; |
michael@0 | 5720 | var argsArrayLen = argsArray.length; |
michael@0 | 5721 | |
michael@0 | 5722 | // Sometimes the OperatorList to execute is empty. |
michael@0 | 5723 | if (argsArrayLen == i) { |
michael@0 | 5724 | return i; |
michael@0 | 5725 | } |
michael@0 | 5726 | |
michael@0 | 5727 | var endTime = Date.now() + EXECUTION_TIME; |
michael@0 | 5728 | |
michael@0 | 5729 | var commonObjs = this.commonObjs; |
michael@0 | 5730 | var objs = this.objs; |
michael@0 | 5731 | var fnId; |
michael@0 | 5732 | var deferred = Promise.resolve(); |
michael@0 | 5733 | |
michael@0 | 5734 | while (true) { |
michael@0 | 5735 | if (stepper && i === stepper.nextBreakPoint) { |
michael@0 | 5736 | stepper.breakIt(i, continueCallback); |
michael@0 | 5737 | return i; |
michael@0 | 5738 | } |
michael@0 | 5739 | |
michael@0 | 5740 | fnId = fnArray[i]; |
michael@0 | 5741 | |
michael@0 | 5742 | if (fnId !== OPS.dependency) { |
michael@0 | 5743 | this[fnId].apply(this, argsArray[i]); |
michael@0 | 5744 | } else { |
michael@0 | 5745 | var deps = argsArray[i]; |
michael@0 | 5746 | for (var n = 0, nn = deps.length; n < nn; n++) { |
michael@0 | 5747 | var depObjId = deps[n]; |
michael@0 | 5748 | var common = depObjId.substring(0, 2) == 'g_'; |
michael@0 | 5749 | |
michael@0 | 5750 | // If the promise isn't resolved yet, add the continueCallback |
michael@0 | 5751 | // to the promise and bail out. |
michael@0 | 5752 | if (!common && !objs.isResolved(depObjId)) { |
michael@0 | 5753 | objs.get(depObjId, continueCallback); |
michael@0 | 5754 | return i; |
michael@0 | 5755 | } |
michael@0 | 5756 | if (common && !commonObjs.isResolved(depObjId)) { |
michael@0 | 5757 | commonObjs.get(depObjId, continueCallback); |
michael@0 | 5758 | return i; |
michael@0 | 5759 | } |
michael@0 | 5760 | } |
michael@0 | 5761 | } |
michael@0 | 5762 | |
michael@0 | 5763 | i++; |
michael@0 | 5764 | |
michael@0 | 5765 | // If the entire operatorList was executed, stop as were done. |
michael@0 | 5766 | if (i == argsArrayLen) { |
michael@0 | 5767 | return i; |
michael@0 | 5768 | } |
michael@0 | 5769 | |
michael@0 | 5770 | // If the execution took longer then a certain amount of time, schedule |
michael@0 | 5771 | // to continue exeution after a short delay. |
michael@0 | 5772 | // However, this is only possible if a 'continueCallback' is passed in. |
michael@0 | 5773 | if (continueCallback && Date.now() > endTime) { |
michael@0 | 5774 | deferred.then(continueCallback); |
michael@0 | 5775 | return i; |
michael@0 | 5776 | } |
michael@0 | 5777 | |
michael@0 | 5778 | // If the operatorList isn't executed completely yet OR the execution |
michael@0 | 5779 | // time was short enough, do another execution round. |
michael@0 | 5780 | } |
michael@0 | 5781 | }, |
michael@0 | 5782 | |
michael@0 | 5783 | endDrawing: function CanvasGraphics_endDrawing() { |
michael@0 | 5784 | this.ctx.restore(); |
michael@0 | 5785 | CachedCanvases.clear(); |
michael@0 | 5786 | WebGLUtils.clear(); |
michael@0 | 5787 | |
michael@0 | 5788 | if (this.imageLayer) { |
michael@0 | 5789 | this.imageLayer.endLayout(); |
michael@0 | 5790 | } |
michael@0 | 5791 | }, |
michael@0 | 5792 | |
michael@0 | 5793 | // Graphics state |
michael@0 | 5794 | setLineWidth: function CanvasGraphics_setLineWidth(width) { |
michael@0 | 5795 | this.current.lineWidth = width; |
michael@0 | 5796 | this.ctx.lineWidth = width; |
michael@0 | 5797 | }, |
michael@0 | 5798 | setLineCap: function CanvasGraphics_setLineCap(style) { |
michael@0 | 5799 | this.ctx.lineCap = LINE_CAP_STYLES[style]; |
michael@0 | 5800 | }, |
michael@0 | 5801 | setLineJoin: function CanvasGraphics_setLineJoin(style) { |
michael@0 | 5802 | this.ctx.lineJoin = LINE_JOIN_STYLES[style]; |
michael@0 | 5803 | }, |
michael@0 | 5804 | setMiterLimit: function CanvasGraphics_setMiterLimit(limit) { |
michael@0 | 5805 | this.ctx.miterLimit = limit; |
michael@0 | 5806 | }, |
michael@0 | 5807 | setDash: function CanvasGraphics_setDash(dashArray, dashPhase) { |
michael@0 | 5808 | var ctx = this.ctx; |
michael@0 | 5809 | if ('setLineDash' in ctx) { |
michael@0 | 5810 | ctx.setLineDash(dashArray); |
michael@0 | 5811 | ctx.lineDashOffset = dashPhase; |
michael@0 | 5812 | } else { |
michael@0 | 5813 | ctx.mozDash = dashArray; |
michael@0 | 5814 | ctx.mozDashOffset = dashPhase; |
michael@0 | 5815 | } |
michael@0 | 5816 | }, |
michael@0 | 5817 | setRenderingIntent: function CanvasGraphics_setRenderingIntent(intent) { |
michael@0 | 5818 | // Maybe if we one day fully support color spaces this will be important |
michael@0 | 5819 | // for now we can ignore. |
michael@0 | 5820 | // TODO set rendering intent? |
michael@0 | 5821 | }, |
michael@0 | 5822 | setFlatness: function CanvasGraphics_setFlatness(flatness) { |
michael@0 | 5823 | // There's no way to control this with canvas, but we can safely ignore. |
michael@0 | 5824 | // TODO set flatness? |
michael@0 | 5825 | }, |
michael@0 | 5826 | setGState: function CanvasGraphics_setGState(states) { |
michael@0 | 5827 | for (var i = 0, ii = states.length; i < ii; i++) { |
michael@0 | 5828 | var state = states[i]; |
michael@0 | 5829 | var key = state[0]; |
michael@0 | 5830 | var value = state[1]; |
michael@0 | 5831 | |
michael@0 | 5832 | switch (key) { |
michael@0 | 5833 | case 'LW': |
michael@0 | 5834 | this.setLineWidth(value); |
michael@0 | 5835 | break; |
michael@0 | 5836 | case 'LC': |
michael@0 | 5837 | this.setLineCap(value); |
michael@0 | 5838 | break; |
michael@0 | 5839 | case 'LJ': |
michael@0 | 5840 | this.setLineJoin(value); |
michael@0 | 5841 | break; |
michael@0 | 5842 | case 'ML': |
michael@0 | 5843 | this.setMiterLimit(value); |
michael@0 | 5844 | break; |
michael@0 | 5845 | case 'D': |
michael@0 | 5846 | this.setDash(value[0], value[1]); |
michael@0 | 5847 | break; |
michael@0 | 5848 | case 'RI': |
michael@0 | 5849 | this.setRenderingIntent(value); |
michael@0 | 5850 | break; |
michael@0 | 5851 | case 'FL': |
michael@0 | 5852 | this.setFlatness(value); |
michael@0 | 5853 | break; |
michael@0 | 5854 | case 'Font': |
michael@0 | 5855 | this.setFont(value[0], value[1]); |
michael@0 | 5856 | break; |
michael@0 | 5857 | case 'CA': |
michael@0 | 5858 | this.current.strokeAlpha = state[1]; |
michael@0 | 5859 | break; |
michael@0 | 5860 | case 'ca': |
michael@0 | 5861 | this.current.fillAlpha = state[1]; |
michael@0 | 5862 | this.ctx.globalAlpha = state[1]; |
michael@0 | 5863 | break; |
michael@0 | 5864 | case 'BM': |
michael@0 | 5865 | if (value && value.name && (value.name !== 'Normal')) { |
michael@0 | 5866 | var mode = value.name.replace(/([A-Z])/g, |
michael@0 | 5867 | function(c) { |
michael@0 | 5868 | return '-' + c.toLowerCase(); |
michael@0 | 5869 | } |
michael@0 | 5870 | ).substring(1); |
michael@0 | 5871 | this.ctx.globalCompositeOperation = mode; |
michael@0 | 5872 | if (this.ctx.globalCompositeOperation !== mode) { |
michael@0 | 5873 | warn('globalCompositeOperation "' + mode + |
michael@0 | 5874 | '" is not supported'); |
michael@0 | 5875 | } |
michael@0 | 5876 | } else { |
michael@0 | 5877 | this.ctx.globalCompositeOperation = 'source-over'; |
michael@0 | 5878 | } |
michael@0 | 5879 | break; |
michael@0 | 5880 | case 'SMask': |
michael@0 | 5881 | if (this.current.activeSMask) { |
michael@0 | 5882 | this.endSMaskGroup(); |
michael@0 | 5883 | } |
michael@0 | 5884 | this.current.activeSMask = value ? this.tempSMask : null; |
michael@0 | 5885 | if (this.current.activeSMask) { |
michael@0 | 5886 | this.beginSMaskGroup(); |
michael@0 | 5887 | } |
michael@0 | 5888 | this.tempSMask = null; |
michael@0 | 5889 | break; |
michael@0 | 5890 | } |
michael@0 | 5891 | } |
michael@0 | 5892 | }, |
michael@0 | 5893 | beginSMaskGroup: function CanvasGraphics_beginSMaskGroup() { |
michael@0 | 5894 | |
michael@0 | 5895 | var activeSMask = this.current.activeSMask; |
michael@0 | 5896 | var drawnWidth = activeSMask.canvas.width; |
michael@0 | 5897 | var drawnHeight = activeSMask.canvas.height; |
michael@0 | 5898 | var cacheId = 'smaskGroupAt' + this.groupLevel; |
michael@0 | 5899 | var scratchCanvas = CachedCanvases.getCanvas( |
michael@0 | 5900 | cacheId, drawnWidth, drawnHeight, true); |
michael@0 | 5901 | |
michael@0 | 5902 | var currentCtx = this.ctx; |
michael@0 | 5903 | var currentTransform = currentCtx.mozCurrentTransform; |
michael@0 | 5904 | this.ctx.save(); |
michael@0 | 5905 | |
michael@0 | 5906 | var groupCtx = scratchCanvas.context; |
michael@0 | 5907 | groupCtx.scale(1 / activeSMask.scaleX, 1 / activeSMask.scaleY); |
michael@0 | 5908 | groupCtx.translate(-activeSMask.offsetX, -activeSMask.offsetY); |
michael@0 | 5909 | groupCtx.transform.apply(groupCtx, currentTransform); |
michael@0 | 5910 | |
michael@0 | 5911 | copyCtxState(currentCtx, groupCtx); |
michael@0 | 5912 | this.ctx = groupCtx; |
michael@0 | 5913 | this.setGState([ |
michael@0 | 5914 | ['BM', 'Normal'], |
michael@0 | 5915 | ['ca', 1], |
michael@0 | 5916 | ['CA', 1] |
michael@0 | 5917 | ]); |
michael@0 | 5918 | this.groupStack.push(currentCtx); |
michael@0 | 5919 | this.groupLevel++; |
michael@0 | 5920 | }, |
michael@0 | 5921 | endSMaskGroup: function CanvasGraphics_endSMaskGroup() { |
michael@0 | 5922 | var groupCtx = this.ctx; |
michael@0 | 5923 | this.groupLevel--; |
michael@0 | 5924 | this.ctx = this.groupStack.pop(); |
michael@0 | 5925 | |
michael@0 | 5926 | composeSMask(this.ctx, this.current.activeSMask, groupCtx); |
michael@0 | 5927 | this.ctx.restore(); |
michael@0 | 5928 | }, |
michael@0 | 5929 | save: function CanvasGraphics_save() { |
michael@0 | 5930 | this.ctx.save(); |
michael@0 | 5931 | var old = this.current; |
michael@0 | 5932 | this.stateStack.push(old); |
michael@0 | 5933 | this.current = old.clone(); |
michael@0 | 5934 | if (this.current.activeSMask) { |
michael@0 | 5935 | this.current.activeSMask = null; |
michael@0 | 5936 | } |
michael@0 | 5937 | }, |
michael@0 | 5938 | restore: function CanvasGraphics_restore() { |
michael@0 | 5939 | var prev = this.stateStack.pop(); |
michael@0 | 5940 | if (prev) { |
michael@0 | 5941 | if (this.current.activeSMask) { |
michael@0 | 5942 | this.endSMaskGroup(); |
michael@0 | 5943 | } |
michael@0 | 5944 | |
michael@0 | 5945 | this.current = prev; |
michael@0 | 5946 | this.ctx.restore(); |
michael@0 | 5947 | } |
michael@0 | 5948 | }, |
michael@0 | 5949 | transform: function CanvasGraphics_transform(a, b, c, d, e, f) { |
michael@0 | 5950 | this.ctx.transform(a, b, c, d, e, f); |
michael@0 | 5951 | }, |
michael@0 | 5952 | |
michael@0 | 5953 | // Path |
michael@0 | 5954 | moveTo: function CanvasGraphics_moveTo(x, y) { |
michael@0 | 5955 | this.ctx.moveTo(x, y); |
michael@0 | 5956 | this.current.setCurrentPoint(x, y); |
michael@0 | 5957 | }, |
michael@0 | 5958 | lineTo: function CanvasGraphics_lineTo(x, y) { |
michael@0 | 5959 | this.ctx.lineTo(x, y); |
michael@0 | 5960 | this.current.setCurrentPoint(x, y); |
michael@0 | 5961 | }, |
michael@0 | 5962 | curveTo: function CanvasGraphics_curveTo(x1, y1, x2, y2, x3, y3) { |
michael@0 | 5963 | this.ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3); |
michael@0 | 5964 | this.current.setCurrentPoint(x3, y3); |
michael@0 | 5965 | }, |
michael@0 | 5966 | curveTo2: function CanvasGraphics_curveTo2(x2, y2, x3, y3) { |
michael@0 | 5967 | var current = this.current; |
michael@0 | 5968 | this.ctx.bezierCurveTo(current.x, current.y, x2, y2, x3, y3); |
michael@0 | 5969 | current.setCurrentPoint(x3, y3); |
michael@0 | 5970 | }, |
michael@0 | 5971 | curveTo3: function CanvasGraphics_curveTo3(x1, y1, x3, y3) { |
michael@0 | 5972 | this.curveTo(x1, y1, x3, y3, x3, y3); |
michael@0 | 5973 | this.current.setCurrentPoint(x3, y3); |
michael@0 | 5974 | }, |
michael@0 | 5975 | closePath: function CanvasGraphics_closePath() { |
michael@0 | 5976 | this.ctx.closePath(); |
michael@0 | 5977 | }, |
michael@0 | 5978 | rectangle: function CanvasGraphics_rectangle(x, y, width, height) { |
michael@0 | 5979 | if (width === 0) { |
michael@0 | 5980 | width = this.getSinglePixelWidth(); |
michael@0 | 5981 | } |
michael@0 | 5982 | if (height === 0) { |
michael@0 | 5983 | height = this.getSinglePixelWidth(); |
michael@0 | 5984 | } |
michael@0 | 5985 | |
michael@0 | 5986 | this.ctx.rect(x, y, width, height); |
michael@0 | 5987 | }, |
michael@0 | 5988 | stroke: function CanvasGraphics_stroke(consumePath) { |
michael@0 | 5989 | consumePath = typeof consumePath !== 'undefined' ? consumePath : true; |
michael@0 | 5990 | var ctx = this.ctx; |
michael@0 | 5991 | var strokeColor = this.current.strokeColor; |
michael@0 | 5992 | if (this.current.lineWidth === 0) { |
michael@0 | 5993 | ctx.lineWidth = this.getSinglePixelWidth(); |
michael@0 | 5994 | } |
michael@0 | 5995 | // For stroke we want to temporarily change the global alpha to the |
michael@0 | 5996 | // stroking alpha. |
michael@0 | 5997 | ctx.globalAlpha = this.current.strokeAlpha; |
michael@0 | 5998 | if (strokeColor && strokeColor.hasOwnProperty('type') && |
michael@0 | 5999 | strokeColor.type === 'Pattern') { |
michael@0 | 6000 | // for patterns, we transform to pattern space, calculate |
michael@0 | 6001 | // the pattern, call stroke, and restore to user space |
michael@0 | 6002 | ctx.save(); |
michael@0 | 6003 | ctx.strokeStyle = strokeColor.getPattern(ctx, this); |
michael@0 | 6004 | ctx.stroke(); |
michael@0 | 6005 | ctx.restore(); |
michael@0 | 6006 | } else { |
michael@0 | 6007 | ctx.stroke(); |
michael@0 | 6008 | } |
michael@0 | 6009 | if (consumePath) { |
michael@0 | 6010 | this.consumePath(); |
michael@0 | 6011 | } |
michael@0 | 6012 | // Restore the global alpha to the fill alpha |
michael@0 | 6013 | ctx.globalAlpha = this.current.fillAlpha; |
michael@0 | 6014 | }, |
michael@0 | 6015 | closeStroke: function CanvasGraphics_closeStroke() { |
michael@0 | 6016 | this.closePath(); |
michael@0 | 6017 | this.stroke(); |
michael@0 | 6018 | }, |
michael@0 | 6019 | fill: function CanvasGraphics_fill(consumePath) { |
michael@0 | 6020 | consumePath = typeof consumePath !== 'undefined' ? consumePath : true; |
michael@0 | 6021 | var ctx = this.ctx; |
michael@0 | 6022 | var fillColor = this.current.fillColor; |
michael@0 | 6023 | var needRestore = false; |
michael@0 | 6024 | |
michael@0 | 6025 | if (fillColor && fillColor.hasOwnProperty('type') && |
michael@0 | 6026 | fillColor.type === 'Pattern') { |
michael@0 | 6027 | ctx.save(); |
michael@0 | 6028 | ctx.fillStyle = fillColor.getPattern(ctx, this); |
michael@0 | 6029 | needRestore = true; |
michael@0 | 6030 | } |
michael@0 | 6031 | |
michael@0 | 6032 | if (this.pendingEOFill) { |
michael@0 | 6033 | if ('mozFillRule' in this.ctx) { |
michael@0 | 6034 | this.ctx.mozFillRule = 'evenodd'; |
michael@0 | 6035 | this.ctx.fill(); |
michael@0 | 6036 | this.ctx.mozFillRule = 'nonzero'; |
michael@0 | 6037 | } else { |
michael@0 | 6038 | try { |
michael@0 | 6039 | this.ctx.fill('evenodd'); |
michael@0 | 6040 | } catch (ex) { |
michael@0 | 6041 | // shouldn't really happen, but browsers might think differently |
michael@0 | 6042 | this.ctx.fill(); |
michael@0 | 6043 | } |
michael@0 | 6044 | } |
michael@0 | 6045 | this.pendingEOFill = false; |
michael@0 | 6046 | } else { |
michael@0 | 6047 | this.ctx.fill(); |
michael@0 | 6048 | } |
michael@0 | 6049 | |
michael@0 | 6050 | if (needRestore) { |
michael@0 | 6051 | ctx.restore(); |
michael@0 | 6052 | } |
michael@0 | 6053 | if (consumePath) { |
michael@0 | 6054 | this.consumePath(); |
michael@0 | 6055 | } |
michael@0 | 6056 | }, |
michael@0 | 6057 | eoFill: function CanvasGraphics_eoFill() { |
michael@0 | 6058 | this.pendingEOFill = true; |
michael@0 | 6059 | this.fill(); |
michael@0 | 6060 | }, |
michael@0 | 6061 | fillStroke: function CanvasGraphics_fillStroke() { |
michael@0 | 6062 | this.fill(false); |
michael@0 | 6063 | this.stroke(false); |
michael@0 | 6064 | |
michael@0 | 6065 | this.consumePath(); |
michael@0 | 6066 | }, |
michael@0 | 6067 | eoFillStroke: function CanvasGraphics_eoFillStroke() { |
michael@0 | 6068 | this.pendingEOFill = true; |
michael@0 | 6069 | this.fillStroke(); |
michael@0 | 6070 | }, |
michael@0 | 6071 | closeFillStroke: function CanvasGraphics_closeFillStroke() { |
michael@0 | 6072 | this.closePath(); |
michael@0 | 6073 | this.fillStroke(); |
michael@0 | 6074 | }, |
michael@0 | 6075 | closeEOFillStroke: function CanvasGraphics_closeEOFillStroke() { |
michael@0 | 6076 | this.pendingEOFill = true; |
michael@0 | 6077 | this.closePath(); |
michael@0 | 6078 | this.fillStroke(); |
michael@0 | 6079 | }, |
michael@0 | 6080 | endPath: function CanvasGraphics_endPath() { |
michael@0 | 6081 | this.consumePath(); |
michael@0 | 6082 | }, |
michael@0 | 6083 | |
michael@0 | 6084 | // Clipping |
michael@0 | 6085 | clip: function CanvasGraphics_clip() { |
michael@0 | 6086 | this.pendingClip = NORMAL_CLIP; |
michael@0 | 6087 | }, |
michael@0 | 6088 | eoClip: function CanvasGraphics_eoClip() { |
michael@0 | 6089 | this.pendingClip = EO_CLIP; |
michael@0 | 6090 | }, |
michael@0 | 6091 | |
michael@0 | 6092 | // Text |
michael@0 | 6093 | beginText: function CanvasGraphics_beginText() { |
michael@0 | 6094 | this.current.textMatrix = IDENTITY_MATRIX; |
michael@0 | 6095 | this.current.x = this.current.lineX = 0; |
michael@0 | 6096 | this.current.y = this.current.lineY = 0; |
michael@0 | 6097 | }, |
michael@0 | 6098 | endText: function CanvasGraphics_endText() { |
michael@0 | 6099 | if (!('pendingTextPaths' in this)) { |
michael@0 | 6100 | this.ctx.beginPath(); |
michael@0 | 6101 | return; |
michael@0 | 6102 | } |
michael@0 | 6103 | var paths = this.pendingTextPaths; |
michael@0 | 6104 | var ctx = this.ctx; |
michael@0 | 6105 | |
michael@0 | 6106 | ctx.save(); |
michael@0 | 6107 | ctx.beginPath(); |
michael@0 | 6108 | for (var i = 0; i < paths.length; i++) { |
michael@0 | 6109 | var path = paths[i]; |
michael@0 | 6110 | ctx.setTransform.apply(ctx, path.transform); |
michael@0 | 6111 | ctx.translate(path.x, path.y); |
michael@0 | 6112 | path.addToPath(ctx, path.fontSize); |
michael@0 | 6113 | } |
michael@0 | 6114 | ctx.restore(); |
michael@0 | 6115 | ctx.clip(); |
michael@0 | 6116 | ctx.beginPath(); |
michael@0 | 6117 | delete this.pendingTextPaths; |
michael@0 | 6118 | }, |
michael@0 | 6119 | setCharSpacing: function CanvasGraphics_setCharSpacing(spacing) { |
michael@0 | 6120 | this.current.charSpacing = spacing; |
michael@0 | 6121 | }, |
michael@0 | 6122 | setWordSpacing: function CanvasGraphics_setWordSpacing(spacing) { |
michael@0 | 6123 | this.current.wordSpacing = spacing; |
michael@0 | 6124 | }, |
michael@0 | 6125 | setHScale: function CanvasGraphics_setHScale(scale) { |
michael@0 | 6126 | this.current.textHScale = scale / 100; |
michael@0 | 6127 | }, |
michael@0 | 6128 | setLeading: function CanvasGraphics_setLeading(leading) { |
michael@0 | 6129 | this.current.leading = -leading; |
michael@0 | 6130 | }, |
michael@0 | 6131 | setFont: function CanvasGraphics_setFont(fontRefName, size) { |
michael@0 | 6132 | var fontObj = this.commonObjs.get(fontRefName); |
michael@0 | 6133 | var current = this.current; |
michael@0 | 6134 | |
michael@0 | 6135 | if (!fontObj) { |
michael@0 | 6136 | error('Can\'t find font for ' + fontRefName); |
michael@0 | 6137 | } |
michael@0 | 6138 | |
michael@0 | 6139 | current.fontMatrix = (fontObj.fontMatrix ? |
michael@0 | 6140 | fontObj.fontMatrix : FONT_IDENTITY_MATRIX); |
michael@0 | 6141 | |
michael@0 | 6142 | // A valid matrix needs all main diagonal elements to be non-zero |
michael@0 | 6143 | // This also ensures we bypass FF bugzilla bug #719844. |
michael@0 | 6144 | if (current.fontMatrix[0] === 0 || |
michael@0 | 6145 | current.fontMatrix[3] === 0) { |
michael@0 | 6146 | warn('Invalid font matrix for font ' + fontRefName); |
michael@0 | 6147 | } |
michael@0 | 6148 | |
michael@0 | 6149 | // The spec for Tf (setFont) says that 'size' specifies the font 'scale', |
michael@0 | 6150 | // and in some docs this can be negative (inverted x-y axes). |
michael@0 | 6151 | if (size < 0) { |
michael@0 | 6152 | size = -size; |
michael@0 | 6153 | current.fontDirection = -1; |
michael@0 | 6154 | } else { |
michael@0 | 6155 | current.fontDirection = 1; |
michael@0 | 6156 | } |
michael@0 | 6157 | |
michael@0 | 6158 | this.current.font = fontObj; |
michael@0 | 6159 | this.current.fontSize = size; |
michael@0 | 6160 | |
michael@0 | 6161 | if (fontObj.coded) { |
michael@0 | 6162 | return; // we don't need ctx.font for Type3 fonts |
michael@0 | 6163 | } |
michael@0 | 6164 | |
michael@0 | 6165 | var name = fontObj.loadedName || 'sans-serif'; |
michael@0 | 6166 | var bold = fontObj.black ? (fontObj.bold ? 'bolder' : 'bold') : |
michael@0 | 6167 | (fontObj.bold ? 'bold' : 'normal'); |
michael@0 | 6168 | |
michael@0 | 6169 | var italic = fontObj.italic ? 'italic' : 'normal'; |
michael@0 | 6170 | var typeface = '"' + name + '", ' + fontObj.fallbackName; |
michael@0 | 6171 | |
michael@0 | 6172 | // Some font backends cannot handle fonts below certain size. |
michael@0 | 6173 | // Keeping the font at minimal size and using the fontSizeScale to change |
michael@0 | 6174 | // the current transformation matrix before the fillText/strokeText. |
michael@0 | 6175 | // See https://bugzilla.mozilla.org/show_bug.cgi?id=726227 |
michael@0 | 6176 | var browserFontSize = size >= MIN_FONT_SIZE ? size : MIN_FONT_SIZE; |
michael@0 | 6177 | this.current.fontSizeScale = browserFontSize != MIN_FONT_SIZE ? 1.0 : |
michael@0 | 6178 | size / MIN_FONT_SIZE; |
michael@0 | 6179 | |
michael@0 | 6180 | var rule = italic + ' ' + bold + ' ' + browserFontSize + 'px ' + typeface; |
michael@0 | 6181 | this.ctx.font = rule; |
michael@0 | 6182 | }, |
michael@0 | 6183 | setTextRenderingMode: function CanvasGraphics_setTextRenderingMode(mode) { |
michael@0 | 6184 | this.current.textRenderingMode = mode; |
michael@0 | 6185 | }, |
michael@0 | 6186 | setTextRise: function CanvasGraphics_setTextRise(rise) { |
michael@0 | 6187 | this.current.textRise = rise; |
michael@0 | 6188 | }, |
michael@0 | 6189 | moveText: function CanvasGraphics_moveText(x, y) { |
michael@0 | 6190 | this.current.x = this.current.lineX += x; |
michael@0 | 6191 | this.current.y = this.current.lineY += y; |
michael@0 | 6192 | }, |
michael@0 | 6193 | setLeadingMoveText: function CanvasGraphics_setLeadingMoveText(x, y) { |
michael@0 | 6194 | this.setLeading(-y); |
michael@0 | 6195 | this.moveText(x, y); |
michael@0 | 6196 | }, |
michael@0 | 6197 | setTextMatrix: function CanvasGraphics_setTextMatrix(a, b, c, d, e, f) { |
michael@0 | 6198 | this.current.textMatrix = [a, b, c, d, e, f]; |
michael@0 | 6199 | |
michael@0 | 6200 | this.current.x = this.current.lineX = 0; |
michael@0 | 6201 | this.current.y = this.current.lineY = 0; |
michael@0 | 6202 | }, |
michael@0 | 6203 | nextLine: function CanvasGraphics_nextLine() { |
michael@0 | 6204 | this.moveText(0, this.current.leading); |
michael@0 | 6205 | }, |
michael@0 | 6206 | applyTextTransforms: function CanvasGraphics_applyTextTransforms() { |
michael@0 | 6207 | var ctx = this.ctx; |
michael@0 | 6208 | var current = this.current; |
michael@0 | 6209 | ctx.transform.apply(ctx, current.textMatrix); |
michael@0 | 6210 | ctx.translate(current.x, current.y + current.textRise); |
michael@0 | 6211 | if (current.fontDirection > 0) { |
michael@0 | 6212 | ctx.scale(current.textHScale, -1); |
michael@0 | 6213 | } else { |
michael@0 | 6214 | ctx.scale(-current.textHScale, 1); |
michael@0 | 6215 | } |
michael@0 | 6216 | }, |
michael@0 | 6217 | |
michael@0 | 6218 | paintChar: function (character, x, y) { |
michael@0 | 6219 | var ctx = this.ctx; |
michael@0 | 6220 | var current = this.current; |
michael@0 | 6221 | var font = current.font; |
michael@0 | 6222 | var fontSize = current.fontSize / current.fontSizeScale; |
michael@0 | 6223 | var textRenderingMode = current.textRenderingMode; |
michael@0 | 6224 | var fillStrokeMode = textRenderingMode & |
michael@0 | 6225 | TextRenderingMode.FILL_STROKE_MASK; |
michael@0 | 6226 | var isAddToPathSet = !!(textRenderingMode & |
michael@0 | 6227 | TextRenderingMode.ADD_TO_PATH_FLAG); |
michael@0 | 6228 | |
michael@0 | 6229 | var addToPath; |
michael@0 | 6230 | if (font.disableFontFace || isAddToPathSet) { |
michael@0 | 6231 | addToPath = font.getPathGenerator(this.commonObjs, character); |
michael@0 | 6232 | } |
michael@0 | 6233 | |
michael@0 | 6234 | if (font.disableFontFace) { |
michael@0 | 6235 | ctx.save(); |
michael@0 | 6236 | ctx.translate(x, y); |
michael@0 | 6237 | ctx.beginPath(); |
michael@0 | 6238 | addToPath(ctx, fontSize); |
michael@0 | 6239 | if (fillStrokeMode === TextRenderingMode.FILL || |
michael@0 | 6240 | fillStrokeMode === TextRenderingMode.FILL_STROKE) { |
michael@0 | 6241 | ctx.fill(); |
michael@0 | 6242 | } |
michael@0 | 6243 | if (fillStrokeMode === TextRenderingMode.STROKE || |
michael@0 | 6244 | fillStrokeMode === TextRenderingMode.FILL_STROKE) { |
michael@0 | 6245 | ctx.stroke(); |
michael@0 | 6246 | } |
michael@0 | 6247 | ctx.restore(); |
michael@0 | 6248 | } else { |
michael@0 | 6249 | if (fillStrokeMode === TextRenderingMode.FILL || |
michael@0 | 6250 | fillStrokeMode === TextRenderingMode.FILL_STROKE) { |
michael@0 | 6251 | ctx.fillText(character, x, y); |
michael@0 | 6252 | } |
michael@0 | 6253 | if (fillStrokeMode === TextRenderingMode.STROKE || |
michael@0 | 6254 | fillStrokeMode === TextRenderingMode.FILL_STROKE) { |
michael@0 | 6255 | ctx.strokeText(character, x, y); |
michael@0 | 6256 | } |
michael@0 | 6257 | } |
michael@0 | 6258 | |
michael@0 | 6259 | if (isAddToPathSet) { |
michael@0 | 6260 | var paths = this.pendingTextPaths || (this.pendingTextPaths = []); |
michael@0 | 6261 | paths.push({ |
michael@0 | 6262 | transform: ctx.mozCurrentTransform, |
michael@0 | 6263 | x: x, |
michael@0 | 6264 | y: y, |
michael@0 | 6265 | fontSize: fontSize, |
michael@0 | 6266 | addToPath: addToPath |
michael@0 | 6267 | }); |
michael@0 | 6268 | } |
michael@0 | 6269 | }, |
michael@0 | 6270 | |
michael@0 | 6271 | get isFontSubpixelAAEnabled() { |
michael@0 | 6272 | // Checks if anti-aliasing is enabled when scaled text is painted. |
michael@0 | 6273 | // On Windows GDI scaled fonts looks bad. |
michael@0 | 6274 | var ctx = document.createElement('canvas').getContext('2d'); |
michael@0 | 6275 | ctx.scale(1.5, 1); |
michael@0 | 6276 | ctx.fillText('I', 0, 10); |
michael@0 | 6277 | var data = ctx.getImageData(0, 0, 10, 10).data; |
michael@0 | 6278 | var enabled = false; |
michael@0 | 6279 | for (var i = 3; i < data.length; i += 4) { |
michael@0 | 6280 | if (data[i] > 0 && data[i] < 255) { |
michael@0 | 6281 | enabled = true; |
michael@0 | 6282 | break; |
michael@0 | 6283 | } |
michael@0 | 6284 | } |
michael@0 | 6285 | return shadow(this, 'isFontSubpixelAAEnabled', enabled); |
michael@0 | 6286 | }, |
michael@0 | 6287 | |
michael@0 | 6288 | showText: function CanvasGraphics_showText(glyphs) { |
michael@0 | 6289 | var ctx = this.ctx; |
michael@0 | 6290 | var current = this.current; |
michael@0 | 6291 | var font = current.font; |
michael@0 | 6292 | var fontSize = current.fontSize; |
michael@0 | 6293 | var fontSizeScale = current.fontSizeScale; |
michael@0 | 6294 | var charSpacing = current.charSpacing; |
michael@0 | 6295 | var wordSpacing = current.wordSpacing; |
michael@0 | 6296 | var textHScale = current.textHScale * current.fontDirection; |
michael@0 | 6297 | var fontMatrix = current.fontMatrix || FONT_IDENTITY_MATRIX; |
michael@0 | 6298 | var glyphsLength = glyphs.length; |
michael@0 | 6299 | var vertical = font.vertical; |
michael@0 | 6300 | var defaultVMetrics = font.defaultVMetrics; |
michael@0 | 6301 | var i, glyph, width; |
michael@0 | 6302 | |
michael@0 | 6303 | if (fontSize === 0) { |
michael@0 | 6304 | return; |
michael@0 | 6305 | } |
michael@0 | 6306 | |
michael@0 | 6307 | // Type3 fonts - each glyph is a "mini-PDF" |
michael@0 | 6308 | if (font.coded) { |
michael@0 | 6309 | ctx.save(); |
michael@0 | 6310 | ctx.transform.apply(ctx, current.textMatrix); |
michael@0 | 6311 | ctx.translate(current.x, current.y); |
michael@0 | 6312 | |
michael@0 | 6313 | ctx.scale(textHScale, 1); |
michael@0 | 6314 | |
michael@0 | 6315 | for (i = 0; i < glyphsLength; ++i) { |
michael@0 | 6316 | glyph = glyphs[i]; |
michael@0 | 6317 | if (glyph === null) { |
michael@0 | 6318 | // word break |
michael@0 | 6319 | this.ctx.translate(wordSpacing, 0); |
michael@0 | 6320 | current.x += wordSpacing * textHScale; |
michael@0 | 6321 | continue; |
michael@0 | 6322 | } |
michael@0 | 6323 | |
michael@0 | 6324 | this.processingType3 = glyph; |
michael@0 | 6325 | this.save(); |
michael@0 | 6326 | ctx.scale(fontSize, fontSize); |
michael@0 | 6327 | ctx.transform.apply(ctx, fontMatrix); |
michael@0 | 6328 | this.executeOperatorList(glyph.operatorList); |
michael@0 | 6329 | this.restore(); |
michael@0 | 6330 | |
michael@0 | 6331 | var transformed = Util.applyTransform([glyph.width, 0], fontMatrix); |
michael@0 | 6332 | width = ((transformed[0] * fontSize + charSpacing) * |
michael@0 | 6333 | current.fontDirection); |
michael@0 | 6334 | |
michael@0 | 6335 | ctx.translate(width, 0); |
michael@0 | 6336 | current.x += width * textHScale; |
michael@0 | 6337 | } |
michael@0 | 6338 | ctx.restore(); |
michael@0 | 6339 | this.processingType3 = null; |
michael@0 | 6340 | } else { |
michael@0 | 6341 | ctx.save(); |
michael@0 | 6342 | this.applyTextTransforms(); |
michael@0 | 6343 | |
michael@0 | 6344 | var lineWidth = current.lineWidth; |
michael@0 | 6345 | var a1 = current.textMatrix[0], b1 = current.textMatrix[1]; |
michael@0 | 6346 | var scale = Math.sqrt(a1 * a1 + b1 * b1); |
michael@0 | 6347 | if (scale === 0 || lineWidth === 0) { |
michael@0 | 6348 | lineWidth = this.getSinglePixelWidth(); |
michael@0 | 6349 | } else { |
michael@0 | 6350 | lineWidth /= scale; |
michael@0 | 6351 | } |
michael@0 | 6352 | |
michael@0 | 6353 | if (fontSizeScale != 1.0) { |
michael@0 | 6354 | ctx.scale(fontSizeScale, fontSizeScale); |
michael@0 | 6355 | lineWidth /= fontSizeScale; |
michael@0 | 6356 | } |
michael@0 | 6357 | |
michael@0 | 6358 | ctx.lineWidth = lineWidth; |
michael@0 | 6359 | |
michael@0 | 6360 | var x = 0; |
michael@0 | 6361 | for (i = 0; i < glyphsLength; ++i) { |
michael@0 | 6362 | glyph = glyphs[i]; |
michael@0 | 6363 | if (glyph === null) { |
michael@0 | 6364 | // word break |
michael@0 | 6365 | x += current.fontDirection * wordSpacing; |
michael@0 | 6366 | continue; |
michael@0 | 6367 | } |
michael@0 | 6368 | |
michael@0 | 6369 | var restoreNeeded = false; |
michael@0 | 6370 | var character = glyph.fontChar; |
michael@0 | 6371 | var vmetric = glyph.vmetric || defaultVMetrics; |
michael@0 | 6372 | if (vertical) { |
michael@0 | 6373 | var vx = glyph.vmetric ? vmetric[1] : glyph.width * 0.5; |
michael@0 | 6374 | vx = -vx * fontSize * current.fontMatrix[0]; |
michael@0 | 6375 | var vy = vmetric[2] * fontSize * current.fontMatrix[0]; |
michael@0 | 6376 | } |
michael@0 | 6377 | width = vmetric ? -vmetric[0] : glyph.width; |
michael@0 | 6378 | var charWidth = width * fontSize * current.fontMatrix[0] + |
michael@0 | 6379 | charSpacing * current.fontDirection; |
michael@0 | 6380 | var accent = glyph.accent; |
michael@0 | 6381 | |
michael@0 | 6382 | var scaledX, scaledY, scaledAccentX, scaledAccentY; |
michael@0 | 6383 | |
michael@0 | 6384 | if (vertical) { |
michael@0 | 6385 | scaledX = vx / fontSizeScale; |
michael@0 | 6386 | scaledY = (x + vy) / fontSizeScale; |
michael@0 | 6387 | } else { |
michael@0 | 6388 | scaledX = x / fontSizeScale; |
michael@0 | 6389 | scaledY = 0; |
michael@0 | 6390 | } |
michael@0 | 6391 | |
michael@0 | 6392 | if (font.remeasure && width > 0 && this.isFontSubpixelAAEnabled) { |
michael@0 | 6393 | // some standard fonts may not have the exact width, trying to |
michael@0 | 6394 | // rescale per character |
michael@0 | 6395 | var measuredWidth = ctx.measureText(character).width * 1000 / |
michael@0 | 6396 | current.fontSize * current.fontSizeScale; |
michael@0 | 6397 | var characterScaleX = width / measuredWidth; |
michael@0 | 6398 | restoreNeeded = true; |
michael@0 | 6399 | ctx.save(); |
michael@0 | 6400 | ctx.scale(characterScaleX, 1); |
michael@0 | 6401 | scaledX /= characterScaleX; |
michael@0 | 6402 | if (accent) { |
michael@0 | 6403 | scaledAccentX /= characterScaleX; |
michael@0 | 6404 | } |
michael@0 | 6405 | } |
michael@0 | 6406 | |
michael@0 | 6407 | this.paintChar(character, scaledX, scaledY); |
michael@0 | 6408 | if (accent) { |
michael@0 | 6409 | scaledAccentX = scaledX + accent.offset.x / fontSizeScale; |
michael@0 | 6410 | scaledAccentY = scaledY - accent.offset.y / fontSizeScale; |
michael@0 | 6411 | this.paintChar(accent.fontChar, scaledAccentX, scaledAccentY); |
michael@0 | 6412 | } |
michael@0 | 6413 | |
michael@0 | 6414 | x += charWidth; |
michael@0 | 6415 | |
michael@0 | 6416 | if (restoreNeeded) { |
michael@0 | 6417 | ctx.restore(); |
michael@0 | 6418 | } |
michael@0 | 6419 | } |
michael@0 | 6420 | if (vertical) { |
michael@0 | 6421 | current.y -= x * textHScale; |
michael@0 | 6422 | } else { |
michael@0 | 6423 | current.x += x * textHScale; |
michael@0 | 6424 | } |
michael@0 | 6425 | ctx.restore(); |
michael@0 | 6426 | } |
michael@0 | 6427 | }, |
michael@0 | 6428 | showSpacedText: function CanvasGraphics_showSpacedText(arr) { |
michael@0 | 6429 | var current = this.current; |
michael@0 | 6430 | var font = current.font; |
michael@0 | 6431 | var fontSize = current.fontSize; |
michael@0 | 6432 | // TJ array's number is independent from fontMatrix |
michael@0 | 6433 | var textHScale = current.textHScale * 0.001 * current.fontDirection; |
michael@0 | 6434 | var arrLength = arr.length; |
michael@0 | 6435 | var vertical = font.vertical; |
michael@0 | 6436 | |
michael@0 | 6437 | for (var i = 0; i < arrLength; ++i) { |
michael@0 | 6438 | var e = arr[i]; |
michael@0 | 6439 | if (isNum(e)) { |
michael@0 | 6440 | var spacingLength = -e * fontSize * textHScale; |
michael@0 | 6441 | if (vertical) { |
michael@0 | 6442 | current.y += spacingLength; |
michael@0 | 6443 | } else { |
michael@0 | 6444 | current.x += spacingLength; |
michael@0 | 6445 | } |
michael@0 | 6446 | |
michael@0 | 6447 | } else { |
michael@0 | 6448 | this.showText(e); |
michael@0 | 6449 | } |
michael@0 | 6450 | } |
michael@0 | 6451 | }, |
michael@0 | 6452 | nextLineShowText: function CanvasGraphics_nextLineShowText(text) { |
michael@0 | 6453 | this.nextLine(); |
michael@0 | 6454 | this.showText(text); |
michael@0 | 6455 | }, |
michael@0 | 6456 | nextLineSetSpacingShowText: |
michael@0 | 6457 | function CanvasGraphics_nextLineSetSpacingShowText(wordSpacing, |
michael@0 | 6458 | charSpacing, |
michael@0 | 6459 | text) { |
michael@0 | 6460 | this.setWordSpacing(wordSpacing); |
michael@0 | 6461 | this.setCharSpacing(charSpacing); |
michael@0 | 6462 | this.nextLineShowText(text); |
michael@0 | 6463 | }, |
michael@0 | 6464 | |
michael@0 | 6465 | // Type3 fonts |
michael@0 | 6466 | setCharWidth: function CanvasGraphics_setCharWidth(xWidth, yWidth) { |
michael@0 | 6467 | // We can safely ignore this since the width should be the same |
michael@0 | 6468 | // as the width in the Widths array. |
michael@0 | 6469 | }, |
michael@0 | 6470 | setCharWidthAndBounds: function CanvasGraphics_setCharWidthAndBounds(xWidth, |
michael@0 | 6471 | yWidth, |
michael@0 | 6472 | llx, |
michael@0 | 6473 | lly, |
michael@0 | 6474 | urx, |
michael@0 | 6475 | ury) { |
michael@0 | 6476 | // TODO According to the spec we're also suppose to ignore any operators |
michael@0 | 6477 | // that set color or include images while processing this type3 font. |
michael@0 | 6478 | this.rectangle(llx, lly, urx - llx, ury - lly); |
michael@0 | 6479 | this.clip(); |
michael@0 | 6480 | this.endPath(); |
michael@0 | 6481 | }, |
michael@0 | 6482 | |
michael@0 | 6483 | // Color |
michael@0 | 6484 | setStrokeColorSpace: function CanvasGraphics_setStrokeColorSpace(raw) { |
michael@0 | 6485 | this.current.strokeColorSpace = ColorSpace.fromIR(raw); |
michael@0 | 6486 | }, |
michael@0 | 6487 | setFillColorSpace: function CanvasGraphics_setFillColorSpace(raw) { |
michael@0 | 6488 | this.current.fillColorSpace = ColorSpace.fromIR(raw); |
michael@0 | 6489 | }, |
michael@0 | 6490 | setStrokeColor: function CanvasGraphics_setStrokeColor(/*...*/) { |
michael@0 | 6491 | var cs = this.current.strokeColorSpace; |
michael@0 | 6492 | var rgbColor = cs.getRgb(arguments, 0); |
michael@0 | 6493 | var color = Util.makeCssRgb(rgbColor); |
michael@0 | 6494 | this.ctx.strokeStyle = color; |
michael@0 | 6495 | this.current.strokeColor = color; |
michael@0 | 6496 | }, |
michael@0 | 6497 | getColorN_Pattern: function CanvasGraphics_getColorN_Pattern(IR, cs) { |
michael@0 | 6498 | var pattern; |
michael@0 | 6499 | if (IR[0] == 'TilingPattern') { |
michael@0 | 6500 | var args = IR[1]; |
michael@0 | 6501 | var base = cs.base; |
michael@0 | 6502 | var color; |
michael@0 | 6503 | if (base) { |
michael@0 | 6504 | color = base.getRgb(args, 0); |
michael@0 | 6505 | } |
michael@0 | 6506 | pattern = new TilingPattern(IR, color, this.ctx, this.objs, |
michael@0 | 6507 | this.commonObjs, this.baseTransform); |
michael@0 | 6508 | } else { |
michael@0 | 6509 | pattern = getShadingPatternFromIR(IR); |
michael@0 | 6510 | } |
michael@0 | 6511 | return pattern; |
michael@0 | 6512 | }, |
michael@0 | 6513 | setStrokeColorN: function CanvasGraphics_setStrokeColorN(/*...*/) { |
michael@0 | 6514 | var cs = this.current.strokeColorSpace; |
michael@0 | 6515 | |
michael@0 | 6516 | if (cs.name == 'Pattern') { |
michael@0 | 6517 | this.current.strokeColor = this.getColorN_Pattern(arguments, cs); |
michael@0 | 6518 | } else { |
michael@0 | 6519 | this.setStrokeColor.apply(this, arguments); |
michael@0 | 6520 | } |
michael@0 | 6521 | }, |
michael@0 | 6522 | setFillColor: function CanvasGraphics_setFillColor(/*...*/) { |
michael@0 | 6523 | var cs = this.current.fillColorSpace; |
michael@0 | 6524 | var rgbColor = cs.getRgb(arguments, 0); |
michael@0 | 6525 | var color = Util.makeCssRgb(rgbColor); |
michael@0 | 6526 | this.ctx.fillStyle = color; |
michael@0 | 6527 | this.current.fillColor = color; |
michael@0 | 6528 | }, |
michael@0 | 6529 | setFillColorN: function CanvasGraphics_setFillColorN(/*...*/) { |
michael@0 | 6530 | var cs = this.current.fillColorSpace; |
michael@0 | 6531 | |
michael@0 | 6532 | if (cs.name == 'Pattern') { |
michael@0 | 6533 | this.current.fillColor = this.getColorN_Pattern(arguments, cs); |
michael@0 | 6534 | } else { |
michael@0 | 6535 | this.setFillColor.apply(this, arguments); |
michael@0 | 6536 | } |
michael@0 | 6537 | }, |
michael@0 | 6538 | setStrokeGray: function CanvasGraphics_setStrokeGray(gray) { |
michael@0 | 6539 | this.current.strokeColorSpace = ColorSpace.singletons.gray; |
michael@0 | 6540 | |
michael@0 | 6541 | var rgbColor = this.current.strokeColorSpace.getRgb(arguments, 0); |
michael@0 | 6542 | var color = Util.makeCssRgb(rgbColor); |
michael@0 | 6543 | this.ctx.strokeStyle = color; |
michael@0 | 6544 | this.current.strokeColor = color; |
michael@0 | 6545 | }, |
michael@0 | 6546 | setFillGray: function CanvasGraphics_setFillGray(gray) { |
michael@0 | 6547 | this.current.fillColorSpace = ColorSpace.singletons.gray; |
michael@0 | 6548 | |
michael@0 | 6549 | var rgbColor = this.current.fillColorSpace.getRgb(arguments, 0); |
michael@0 | 6550 | var color = Util.makeCssRgb(rgbColor); |
michael@0 | 6551 | this.ctx.fillStyle = color; |
michael@0 | 6552 | this.current.fillColor = color; |
michael@0 | 6553 | }, |
michael@0 | 6554 | setStrokeRGBColor: function CanvasGraphics_setStrokeRGBColor(r, g, b) { |
michael@0 | 6555 | this.current.strokeColorSpace = ColorSpace.singletons.rgb; |
michael@0 | 6556 | |
michael@0 | 6557 | var rgbColor = this.current.strokeColorSpace.getRgb(arguments, 0); |
michael@0 | 6558 | var color = Util.makeCssRgb(rgbColor); |
michael@0 | 6559 | this.ctx.strokeStyle = color; |
michael@0 | 6560 | this.current.strokeColor = color; |
michael@0 | 6561 | }, |
michael@0 | 6562 | setFillRGBColor: function CanvasGraphics_setFillRGBColor(r, g, b) { |
michael@0 | 6563 | this.current.fillColorSpace = ColorSpace.singletons.rgb; |
michael@0 | 6564 | |
michael@0 | 6565 | var rgbColor = this.current.fillColorSpace.getRgb(arguments, 0); |
michael@0 | 6566 | var color = Util.makeCssRgb(rgbColor); |
michael@0 | 6567 | this.ctx.fillStyle = color; |
michael@0 | 6568 | this.current.fillColor = color; |
michael@0 | 6569 | }, |
michael@0 | 6570 | setStrokeCMYKColor: function CanvasGraphics_setStrokeCMYKColor(c, m, y, k) { |
michael@0 | 6571 | this.current.strokeColorSpace = ColorSpace.singletons.cmyk; |
michael@0 | 6572 | |
michael@0 | 6573 | var color = Util.makeCssCmyk(arguments); |
michael@0 | 6574 | this.ctx.strokeStyle = color; |
michael@0 | 6575 | this.current.strokeColor = color; |
michael@0 | 6576 | }, |
michael@0 | 6577 | setFillCMYKColor: function CanvasGraphics_setFillCMYKColor(c, m, y, k) { |
michael@0 | 6578 | this.current.fillColorSpace = ColorSpace.singletons.cmyk; |
michael@0 | 6579 | |
michael@0 | 6580 | var color = Util.makeCssCmyk(arguments); |
michael@0 | 6581 | this.ctx.fillStyle = color; |
michael@0 | 6582 | this.current.fillColor = color; |
michael@0 | 6583 | }, |
michael@0 | 6584 | |
michael@0 | 6585 | shadingFill: function CanvasGraphics_shadingFill(patternIR) { |
michael@0 | 6586 | var ctx = this.ctx; |
michael@0 | 6587 | |
michael@0 | 6588 | this.save(); |
michael@0 | 6589 | var pattern = getShadingPatternFromIR(patternIR); |
michael@0 | 6590 | ctx.fillStyle = pattern.getPattern(ctx, this, true); |
michael@0 | 6591 | |
michael@0 | 6592 | var inv = ctx.mozCurrentTransformInverse; |
michael@0 | 6593 | if (inv) { |
michael@0 | 6594 | var canvas = ctx.canvas; |
michael@0 | 6595 | var width = canvas.width; |
michael@0 | 6596 | var height = canvas.height; |
michael@0 | 6597 | |
michael@0 | 6598 | var bl = Util.applyTransform([0, 0], inv); |
michael@0 | 6599 | var br = Util.applyTransform([0, height], inv); |
michael@0 | 6600 | var ul = Util.applyTransform([width, 0], inv); |
michael@0 | 6601 | var ur = Util.applyTransform([width, height], inv); |
michael@0 | 6602 | |
michael@0 | 6603 | var x0 = Math.min(bl[0], br[0], ul[0], ur[0]); |
michael@0 | 6604 | var y0 = Math.min(bl[1], br[1], ul[1], ur[1]); |
michael@0 | 6605 | var x1 = Math.max(bl[0], br[0], ul[0], ur[0]); |
michael@0 | 6606 | var y1 = Math.max(bl[1], br[1], ul[1], ur[1]); |
michael@0 | 6607 | |
michael@0 | 6608 | this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0); |
michael@0 | 6609 | } else { |
michael@0 | 6610 | // HACK to draw the gradient onto an infinite rectangle. |
michael@0 | 6611 | // PDF gradients are drawn across the entire image while |
michael@0 | 6612 | // Canvas only allows gradients to be drawn in a rectangle |
michael@0 | 6613 | // The following bug should allow us to remove this. |
michael@0 | 6614 | // https://bugzilla.mozilla.org/show_bug.cgi?id=664884 |
michael@0 | 6615 | |
michael@0 | 6616 | this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10); |
michael@0 | 6617 | } |
michael@0 | 6618 | |
michael@0 | 6619 | this.restore(); |
michael@0 | 6620 | }, |
michael@0 | 6621 | |
michael@0 | 6622 | // Images |
michael@0 | 6623 | beginInlineImage: function CanvasGraphics_beginInlineImage() { |
michael@0 | 6624 | error('Should not call beginInlineImage'); |
michael@0 | 6625 | }, |
michael@0 | 6626 | beginImageData: function CanvasGraphics_beginImageData() { |
michael@0 | 6627 | error('Should not call beginImageData'); |
michael@0 | 6628 | }, |
michael@0 | 6629 | |
michael@0 | 6630 | paintFormXObjectBegin: function CanvasGraphics_paintFormXObjectBegin(matrix, |
michael@0 | 6631 | bbox) { |
michael@0 | 6632 | this.save(); |
michael@0 | 6633 | this.baseTransformStack.push(this.baseTransform); |
michael@0 | 6634 | |
michael@0 | 6635 | if (matrix && isArray(matrix) && 6 == matrix.length) { |
michael@0 | 6636 | this.transform.apply(this, matrix); |
michael@0 | 6637 | } |
michael@0 | 6638 | |
michael@0 | 6639 | this.baseTransform = this.ctx.mozCurrentTransform; |
michael@0 | 6640 | |
michael@0 | 6641 | if (bbox && isArray(bbox) && 4 == bbox.length) { |
michael@0 | 6642 | var width = bbox[2] - bbox[0]; |
michael@0 | 6643 | var height = bbox[3] - bbox[1]; |
michael@0 | 6644 | this.rectangle(bbox[0], bbox[1], width, height); |
michael@0 | 6645 | this.clip(); |
michael@0 | 6646 | this.endPath(); |
michael@0 | 6647 | } |
michael@0 | 6648 | }, |
michael@0 | 6649 | |
michael@0 | 6650 | paintFormXObjectEnd: function CanvasGraphics_paintFormXObjectEnd() { |
michael@0 | 6651 | this.restore(); |
michael@0 | 6652 | this.baseTransform = this.baseTransformStack.pop(); |
michael@0 | 6653 | }, |
michael@0 | 6654 | |
michael@0 | 6655 | beginGroup: function CanvasGraphics_beginGroup(group) { |
michael@0 | 6656 | this.save(); |
michael@0 | 6657 | var currentCtx = this.ctx; |
michael@0 | 6658 | // TODO non-isolated groups - according to Rik at adobe non-isolated |
michael@0 | 6659 | // group results aren't usually that different and they even have tools |
michael@0 | 6660 | // that ignore this setting. Notes from Rik on implmenting: |
michael@0 | 6661 | // - When you encounter an transparency group, create a new canvas with |
michael@0 | 6662 | // the dimensions of the bbox |
michael@0 | 6663 | // - copy the content from the previous canvas to the new canvas |
michael@0 | 6664 | // - draw as usual |
michael@0 | 6665 | // - remove the backdrop alpha: |
michael@0 | 6666 | // alphaNew = 1 - (1 - alpha)/(1 - alphaBackdrop) with 'alpha' the alpha |
michael@0 | 6667 | // value of your transparency group and 'alphaBackdrop' the alpha of the |
michael@0 | 6668 | // backdrop |
michael@0 | 6669 | // - remove background color: |
michael@0 | 6670 | // colorNew = color - alphaNew *colorBackdrop /(1 - alphaNew) |
michael@0 | 6671 | if (!group.isolated) { |
michael@0 | 6672 | info('TODO: Support non-isolated groups.'); |
michael@0 | 6673 | } |
michael@0 | 6674 | |
michael@0 | 6675 | // TODO knockout - supposedly possible with the clever use of compositing |
michael@0 | 6676 | // modes. |
michael@0 | 6677 | if (group.knockout) { |
michael@0 | 6678 | warn('Knockout groups not supported.'); |
michael@0 | 6679 | } |
michael@0 | 6680 | |
michael@0 | 6681 | var currentTransform = currentCtx.mozCurrentTransform; |
michael@0 | 6682 | if (group.matrix) { |
michael@0 | 6683 | currentCtx.transform.apply(currentCtx, group.matrix); |
michael@0 | 6684 | } |
michael@0 | 6685 | assert(group.bbox, 'Bounding box is required.'); |
michael@0 | 6686 | |
michael@0 | 6687 | // Based on the current transform figure out how big the bounding box |
michael@0 | 6688 | // will actually be. |
michael@0 | 6689 | var bounds = Util.getAxialAlignedBoundingBox( |
michael@0 | 6690 | group.bbox, |
michael@0 | 6691 | currentCtx.mozCurrentTransform); |
michael@0 | 6692 | // Clip the bounding box to the current canvas. |
michael@0 | 6693 | var canvasBounds = [0, |
michael@0 | 6694 | 0, |
michael@0 | 6695 | currentCtx.canvas.width, |
michael@0 | 6696 | currentCtx.canvas.height]; |
michael@0 | 6697 | bounds = Util.intersect(bounds, canvasBounds) || [0, 0, 0, 0]; |
michael@0 | 6698 | // Use ceil in case we're between sizes so we don't create canvas that is |
michael@0 | 6699 | // too small and make the canvas at least 1x1 pixels. |
michael@0 | 6700 | var offsetX = Math.floor(bounds[0]); |
michael@0 | 6701 | var offsetY = Math.floor(bounds[1]); |
michael@0 | 6702 | var drawnWidth = Math.max(Math.ceil(bounds[2]) - offsetX, 1); |
michael@0 | 6703 | var drawnHeight = Math.max(Math.ceil(bounds[3]) - offsetY, 1); |
michael@0 | 6704 | var scaleX = 1, scaleY = 1; |
michael@0 | 6705 | if (drawnWidth > MAX_GROUP_SIZE) { |
michael@0 | 6706 | scaleX = drawnWidth / MAX_GROUP_SIZE; |
michael@0 | 6707 | drawnWidth = MAX_GROUP_SIZE; |
michael@0 | 6708 | } |
michael@0 | 6709 | if (drawnHeight > MAX_GROUP_SIZE) { |
michael@0 | 6710 | scaleY = drawnHeight / MAX_GROUP_SIZE; |
michael@0 | 6711 | drawnHeight = MAX_GROUP_SIZE; |
michael@0 | 6712 | } |
michael@0 | 6713 | |
michael@0 | 6714 | var cacheId = 'groupAt' + this.groupLevel; |
michael@0 | 6715 | if (group.smask) { |
michael@0 | 6716 | // Using two cache entries is case if masks are used one after another. |
michael@0 | 6717 | cacheId += '_smask_' + ((this.smaskCounter++) % 2); |
michael@0 | 6718 | } |
michael@0 | 6719 | var scratchCanvas = CachedCanvases.getCanvas( |
michael@0 | 6720 | cacheId, drawnWidth, drawnHeight, true); |
michael@0 | 6721 | var groupCtx = scratchCanvas.context; |
michael@0 | 6722 | |
michael@0 | 6723 | // Since we created a new canvas that is just the size of the bounding box |
michael@0 | 6724 | // we have to translate the group ctx. |
michael@0 | 6725 | groupCtx.scale(1 / scaleX, 1 / scaleY); |
michael@0 | 6726 | groupCtx.translate(-offsetX, -offsetY); |
michael@0 | 6727 | groupCtx.transform.apply(groupCtx, currentTransform); |
michael@0 | 6728 | |
michael@0 | 6729 | if (group.smask) { |
michael@0 | 6730 | // Saving state and cached mask to be used in setGState. |
michael@0 | 6731 | this.smaskStack.push({ |
michael@0 | 6732 | canvas: scratchCanvas.canvas, |
michael@0 | 6733 | context: groupCtx, |
michael@0 | 6734 | offsetX: offsetX, |
michael@0 | 6735 | offsetY: offsetY, |
michael@0 | 6736 | scaleX: scaleX, |
michael@0 | 6737 | scaleY: scaleY, |
michael@0 | 6738 | subtype: group.smask.subtype, |
michael@0 | 6739 | backdrop: group.smask.backdrop, |
michael@0 | 6740 | colorSpace: group.colorSpace && ColorSpace.fromIR(group.colorSpace) |
michael@0 | 6741 | }); |
michael@0 | 6742 | } else { |
michael@0 | 6743 | // Setup the current ctx so when the group is popped we draw it at the |
michael@0 | 6744 | // right location. |
michael@0 | 6745 | currentCtx.setTransform(1, 0, 0, 1, 0, 0); |
michael@0 | 6746 | currentCtx.translate(offsetX, offsetY); |
michael@0 | 6747 | currentCtx.scale(scaleX, scaleY); |
michael@0 | 6748 | } |
michael@0 | 6749 | // The transparency group inherits all off the current graphics state |
michael@0 | 6750 | // except the blend mode, soft mask, and alpha constants. |
michael@0 | 6751 | copyCtxState(currentCtx, groupCtx); |
michael@0 | 6752 | this.ctx = groupCtx; |
michael@0 | 6753 | this.setGState([ |
michael@0 | 6754 | ['BM', 'Normal'], |
michael@0 | 6755 | ['ca', 1], |
michael@0 | 6756 | ['CA', 1] |
michael@0 | 6757 | ]); |
michael@0 | 6758 | this.groupStack.push(currentCtx); |
michael@0 | 6759 | this.groupLevel++; |
michael@0 | 6760 | }, |
michael@0 | 6761 | |
michael@0 | 6762 | endGroup: function CanvasGraphics_endGroup(group) { |
michael@0 | 6763 | this.groupLevel--; |
michael@0 | 6764 | var groupCtx = this.ctx; |
michael@0 | 6765 | this.ctx = this.groupStack.pop(); |
michael@0 | 6766 | // Turn off image smoothing to avoid sub pixel interpolation which can |
michael@0 | 6767 | // look kind of blurry for some pdfs. |
michael@0 | 6768 | if ('imageSmoothingEnabled' in this.ctx) { |
michael@0 | 6769 | this.ctx.imageSmoothingEnabled = false; |
michael@0 | 6770 | } else { |
michael@0 | 6771 | this.ctx.mozImageSmoothingEnabled = false; |
michael@0 | 6772 | } |
michael@0 | 6773 | if (group.smask) { |
michael@0 | 6774 | this.tempSMask = this.smaskStack.pop(); |
michael@0 | 6775 | } else { |
michael@0 | 6776 | this.ctx.drawImage(groupCtx.canvas, 0, 0); |
michael@0 | 6777 | } |
michael@0 | 6778 | this.restore(); |
michael@0 | 6779 | }, |
michael@0 | 6780 | |
michael@0 | 6781 | beginAnnotations: function CanvasGraphics_beginAnnotations() { |
michael@0 | 6782 | this.save(); |
michael@0 | 6783 | this.current = new CanvasExtraState(); |
michael@0 | 6784 | }, |
michael@0 | 6785 | |
michael@0 | 6786 | endAnnotations: function CanvasGraphics_endAnnotations() { |
michael@0 | 6787 | this.restore(); |
michael@0 | 6788 | }, |
michael@0 | 6789 | |
michael@0 | 6790 | beginAnnotation: function CanvasGraphics_beginAnnotation(rect, transform, |
michael@0 | 6791 | matrix) { |
michael@0 | 6792 | this.save(); |
michael@0 | 6793 | |
michael@0 | 6794 | if (rect && isArray(rect) && 4 == rect.length) { |
michael@0 | 6795 | var width = rect[2] - rect[0]; |
michael@0 | 6796 | var height = rect[3] - rect[1]; |
michael@0 | 6797 | this.rectangle(rect[0], rect[1], width, height); |
michael@0 | 6798 | this.clip(); |
michael@0 | 6799 | this.endPath(); |
michael@0 | 6800 | } |
michael@0 | 6801 | |
michael@0 | 6802 | this.transform.apply(this, transform); |
michael@0 | 6803 | this.transform.apply(this, matrix); |
michael@0 | 6804 | }, |
michael@0 | 6805 | |
michael@0 | 6806 | endAnnotation: function CanvasGraphics_endAnnotation() { |
michael@0 | 6807 | this.restore(); |
michael@0 | 6808 | }, |
michael@0 | 6809 | |
michael@0 | 6810 | paintJpegXObject: function CanvasGraphics_paintJpegXObject(objId, w, h) { |
michael@0 | 6811 | var domImage = this.objs.get(objId); |
michael@0 | 6812 | if (!domImage) { |
michael@0 | 6813 | warn('Dependent image isn\'t ready yet'); |
michael@0 | 6814 | return; |
michael@0 | 6815 | } |
michael@0 | 6816 | |
michael@0 | 6817 | this.save(); |
michael@0 | 6818 | |
michael@0 | 6819 | var ctx = this.ctx; |
michael@0 | 6820 | // scale the image to the unit square |
michael@0 | 6821 | ctx.scale(1 / w, -1 / h); |
michael@0 | 6822 | |
michael@0 | 6823 | ctx.drawImage(domImage, 0, 0, domImage.width, domImage.height, |
michael@0 | 6824 | 0, -h, w, h); |
michael@0 | 6825 | if (this.imageLayer) { |
michael@0 | 6826 | var currentTransform = ctx.mozCurrentTransformInverse; |
michael@0 | 6827 | var position = this.getCanvasPosition(0, 0); |
michael@0 | 6828 | this.imageLayer.appendImage({ |
michael@0 | 6829 | objId: objId, |
michael@0 | 6830 | left: position[0], |
michael@0 | 6831 | top: position[1], |
michael@0 | 6832 | width: w / currentTransform[0], |
michael@0 | 6833 | height: h / currentTransform[3] |
michael@0 | 6834 | }); |
michael@0 | 6835 | } |
michael@0 | 6836 | this.restore(); |
michael@0 | 6837 | }, |
michael@0 | 6838 | |
michael@0 | 6839 | paintImageMaskXObject: function CanvasGraphics_paintImageMaskXObject(img) { |
michael@0 | 6840 | var ctx = this.ctx; |
michael@0 | 6841 | var width = img.width, height = img.height; |
michael@0 | 6842 | |
michael@0 | 6843 | var glyph = this.processingType3; |
michael@0 | 6844 | |
michael@0 | 6845 | if (COMPILE_TYPE3_GLYPHS && glyph && !('compiled' in glyph)) { |
michael@0 | 6846 | var MAX_SIZE_TO_COMPILE = 1000; |
michael@0 | 6847 | if (width <= MAX_SIZE_TO_COMPILE && height <= MAX_SIZE_TO_COMPILE) { |
michael@0 | 6848 | glyph.compiled = |
michael@0 | 6849 | compileType3Glyph({data: img.data, width: width, height: height}); |
michael@0 | 6850 | } else { |
michael@0 | 6851 | glyph.compiled = null; |
michael@0 | 6852 | } |
michael@0 | 6853 | } |
michael@0 | 6854 | |
michael@0 | 6855 | if (glyph && glyph.compiled) { |
michael@0 | 6856 | glyph.compiled(ctx); |
michael@0 | 6857 | return; |
michael@0 | 6858 | } |
michael@0 | 6859 | |
michael@0 | 6860 | var maskCanvas = CachedCanvases.getCanvas('maskCanvas', width, height); |
michael@0 | 6861 | var maskCtx = maskCanvas.context; |
michael@0 | 6862 | maskCtx.save(); |
michael@0 | 6863 | |
michael@0 | 6864 | putBinaryImageMask(maskCtx, img); |
michael@0 | 6865 | |
michael@0 | 6866 | maskCtx.globalCompositeOperation = 'source-in'; |
michael@0 | 6867 | |
michael@0 | 6868 | var fillColor = this.current.fillColor; |
michael@0 | 6869 | maskCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') && |
michael@0 | 6870 | fillColor.type === 'Pattern') ? |
michael@0 | 6871 | fillColor.getPattern(maskCtx, this) : fillColor; |
michael@0 | 6872 | maskCtx.fillRect(0, 0, width, height); |
michael@0 | 6873 | |
michael@0 | 6874 | maskCtx.restore(); |
michael@0 | 6875 | |
michael@0 | 6876 | this.paintInlineImageXObject(maskCanvas.canvas); |
michael@0 | 6877 | }, |
michael@0 | 6878 | |
michael@0 | 6879 | paintImageMaskXObjectRepeat: |
michael@0 | 6880 | function CanvasGraphics_paintImageMaskXObjectRepeat(imgData, scaleX, |
michael@0 | 6881 | scaleY, positions) { |
michael@0 | 6882 | var width = imgData.width; |
michael@0 | 6883 | var height = imgData.height; |
michael@0 | 6884 | var ctx = this.ctx; |
michael@0 | 6885 | |
michael@0 | 6886 | var maskCanvas = CachedCanvases.getCanvas('maskCanvas', width, height); |
michael@0 | 6887 | var maskCtx = maskCanvas.context; |
michael@0 | 6888 | maskCtx.save(); |
michael@0 | 6889 | |
michael@0 | 6890 | putBinaryImageMask(maskCtx, imgData); |
michael@0 | 6891 | |
michael@0 | 6892 | maskCtx.globalCompositeOperation = 'source-in'; |
michael@0 | 6893 | |
michael@0 | 6894 | var fillColor = this.current.fillColor; |
michael@0 | 6895 | maskCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') && |
michael@0 | 6896 | fillColor.type === 'Pattern') ? |
michael@0 | 6897 | fillColor.getPattern(maskCtx, this) : fillColor; |
michael@0 | 6898 | maskCtx.fillRect(0, 0, width, height); |
michael@0 | 6899 | |
michael@0 | 6900 | maskCtx.restore(); |
michael@0 | 6901 | |
michael@0 | 6902 | for (var i = 0, ii = positions.length; i < ii; i += 2) { |
michael@0 | 6903 | ctx.save(); |
michael@0 | 6904 | ctx.transform(scaleX, 0, 0, scaleY, positions[i], positions[i + 1]); |
michael@0 | 6905 | ctx.scale(1, -1); |
michael@0 | 6906 | ctx.drawImage(maskCanvas.canvas, 0, 0, width, height, |
michael@0 | 6907 | 0, -1, 1, 1); |
michael@0 | 6908 | ctx.restore(); |
michael@0 | 6909 | } |
michael@0 | 6910 | }, |
michael@0 | 6911 | |
michael@0 | 6912 | paintImageMaskXObjectGroup: |
michael@0 | 6913 | function CanvasGraphics_paintImageMaskXObjectGroup(images) { |
michael@0 | 6914 | var ctx = this.ctx; |
michael@0 | 6915 | |
michael@0 | 6916 | for (var i = 0, ii = images.length; i < ii; i++) { |
michael@0 | 6917 | var image = images[i]; |
michael@0 | 6918 | var width = image.width, height = image.height; |
michael@0 | 6919 | |
michael@0 | 6920 | var maskCanvas = CachedCanvases.getCanvas('maskCanvas', width, height); |
michael@0 | 6921 | var maskCtx = maskCanvas.context; |
michael@0 | 6922 | maskCtx.save(); |
michael@0 | 6923 | |
michael@0 | 6924 | putBinaryImageMask(maskCtx, image); |
michael@0 | 6925 | |
michael@0 | 6926 | maskCtx.globalCompositeOperation = 'source-in'; |
michael@0 | 6927 | |
michael@0 | 6928 | var fillColor = this.current.fillColor; |
michael@0 | 6929 | maskCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') && |
michael@0 | 6930 | fillColor.type === 'Pattern') ? |
michael@0 | 6931 | fillColor.getPattern(maskCtx, this) : fillColor; |
michael@0 | 6932 | maskCtx.fillRect(0, 0, width, height); |
michael@0 | 6933 | |
michael@0 | 6934 | maskCtx.restore(); |
michael@0 | 6935 | |
michael@0 | 6936 | ctx.save(); |
michael@0 | 6937 | ctx.transform.apply(ctx, image.transform); |
michael@0 | 6938 | ctx.scale(1, -1); |
michael@0 | 6939 | ctx.drawImage(maskCanvas.canvas, 0, 0, width, height, |
michael@0 | 6940 | 0, -1, 1, 1); |
michael@0 | 6941 | ctx.restore(); |
michael@0 | 6942 | } |
michael@0 | 6943 | }, |
michael@0 | 6944 | |
michael@0 | 6945 | paintImageXObject: function CanvasGraphics_paintImageXObject(objId) { |
michael@0 | 6946 | var imgData = this.objs.get(objId); |
michael@0 | 6947 | if (!imgData) { |
michael@0 | 6948 | warn('Dependent image isn\'t ready yet'); |
michael@0 | 6949 | return; |
michael@0 | 6950 | } |
michael@0 | 6951 | |
michael@0 | 6952 | this.paintInlineImageXObject(imgData); |
michael@0 | 6953 | }, |
michael@0 | 6954 | |
michael@0 | 6955 | paintImageXObjectRepeat: |
michael@0 | 6956 | function CanvasGraphics_paintImageXObjectRepeat(objId, scaleX, scaleY, |
michael@0 | 6957 | positions) { |
michael@0 | 6958 | var imgData = this.objs.get(objId); |
michael@0 | 6959 | if (!imgData) { |
michael@0 | 6960 | warn('Dependent image isn\'t ready yet'); |
michael@0 | 6961 | return; |
michael@0 | 6962 | } |
michael@0 | 6963 | |
michael@0 | 6964 | var width = imgData.width; |
michael@0 | 6965 | var height = imgData.height; |
michael@0 | 6966 | var map = []; |
michael@0 | 6967 | for (var i = 0, ii = positions.length; i < ii; i += 2) { |
michael@0 | 6968 | map.push({transform: [scaleX, 0, 0, scaleY, positions[i], |
michael@0 | 6969 | positions[i + 1]], x: 0, y: 0, w: width, h: height}); |
michael@0 | 6970 | } |
michael@0 | 6971 | this.paintInlineImageXObjectGroup(imgData, map); |
michael@0 | 6972 | }, |
michael@0 | 6973 | |
michael@0 | 6974 | paintInlineImageXObject: |
michael@0 | 6975 | function CanvasGraphics_paintInlineImageXObject(imgData) { |
michael@0 | 6976 | var width = imgData.width; |
michael@0 | 6977 | var height = imgData.height; |
michael@0 | 6978 | var ctx = this.ctx; |
michael@0 | 6979 | |
michael@0 | 6980 | this.save(); |
michael@0 | 6981 | // scale the image to the unit square |
michael@0 | 6982 | ctx.scale(1 / width, -1 / height); |
michael@0 | 6983 | |
michael@0 | 6984 | var currentTransform = ctx.mozCurrentTransformInverse; |
michael@0 | 6985 | var a = currentTransform[0], b = currentTransform[1]; |
michael@0 | 6986 | var widthScale = Math.max(Math.sqrt(a * a + b * b), 1); |
michael@0 | 6987 | var c = currentTransform[2], d = currentTransform[3]; |
michael@0 | 6988 | var heightScale = Math.max(Math.sqrt(c * c + d * d), 1); |
michael@0 | 6989 | |
michael@0 | 6990 | var imgToPaint, tmpCanvas; |
michael@0 | 6991 | // instanceof HTMLElement does not work in jsdom node.js module |
michael@0 | 6992 | if (imgData instanceof HTMLElement || !imgData.data) { |
michael@0 | 6993 | imgToPaint = imgData; |
michael@0 | 6994 | } else { |
michael@0 | 6995 | tmpCanvas = CachedCanvases.getCanvas('inlineImage', width, height); |
michael@0 | 6996 | var tmpCtx = tmpCanvas.context; |
michael@0 | 6997 | putBinaryImageData(tmpCtx, imgData); |
michael@0 | 6998 | imgToPaint = tmpCanvas.canvas; |
michael@0 | 6999 | } |
michael@0 | 7000 | |
michael@0 | 7001 | var paintWidth = width, paintHeight = height; |
michael@0 | 7002 | var tmpCanvasId = 'prescale1'; |
michael@0 | 7003 | // Vertial or horizontal scaling shall not be more than 2 to not loose the |
michael@0 | 7004 | // pixels during drawImage operation, painting on the temporary canvas(es) |
michael@0 | 7005 | // that are twice smaller in size |
michael@0 | 7006 | while ((widthScale > 2 && paintWidth > 1) || |
michael@0 | 7007 | (heightScale > 2 && paintHeight > 1)) { |
michael@0 | 7008 | var newWidth = paintWidth, newHeight = paintHeight; |
michael@0 | 7009 | if (widthScale > 2 && paintWidth > 1) { |
michael@0 | 7010 | newWidth = Math.ceil(paintWidth / 2); |
michael@0 | 7011 | widthScale /= paintWidth / newWidth; |
michael@0 | 7012 | } |
michael@0 | 7013 | if (heightScale > 2 && paintHeight > 1) { |
michael@0 | 7014 | newHeight = Math.ceil(paintHeight / 2); |
michael@0 | 7015 | heightScale /= paintHeight / newHeight; |
michael@0 | 7016 | } |
michael@0 | 7017 | tmpCanvas = CachedCanvases.getCanvas(tmpCanvasId, newWidth, newHeight); |
michael@0 | 7018 | tmpCtx = tmpCanvas.context; |
michael@0 | 7019 | tmpCtx.clearRect(0, 0, newWidth, newHeight); |
michael@0 | 7020 | tmpCtx.drawImage(imgToPaint, 0, 0, paintWidth, paintHeight, |
michael@0 | 7021 | 0, 0, newWidth, newHeight); |
michael@0 | 7022 | imgToPaint = tmpCanvas.canvas; |
michael@0 | 7023 | paintWidth = newWidth; |
michael@0 | 7024 | paintHeight = newHeight; |
michael@0 | 7025 | tmpCanvasId = tmpCanvasId === 'prescale1' ? 'prescale2' : 'prescale1'; |
michael@0 | 7026 | } |
michael@0 | 7027 | ctx.drawImage(imgToPaint, 0, 0, paintWidth, paintHeight, |
michael@0 | 7028 | 0, -height, width, height); |
michael@0 | 7029 | |
michael@0 | 7030 | if (this.imageLayer) { |
michael@0 | 7031 | var position = this.getCanvasPosition(0, -height); |
michael@0 | 7032 | this.imageLayer.appendImage({ |
michael@0 | 7033 | imgData: imgData, |
michael@0 | 7034 | left: position[0], |
michael@0 | 7035 | top: position[1], |
michael@0 | 7036 | width: width / currentTransform[0], |
michael@0 | 7037 | height: height / currentTransform[3] |
michael@0 | 7038 | }); |
michael@0 | 7039 | } |
michael@0 | 7040 | this.restore(); |
michael@0 | 7041 | }, |
michael@0 | 7042 | |
michael@0 | 7043 | paintInlineImageXObjectGroup: |
michael@0 | 7044 | function CanvasGraphics_paintInlineImageXObjectGroup(imgData, map) { |
michael@0 | 7045 | var ctx = this.ctx; |
michael@0 | 7046 | var w = imgData.width; |
michael@0 | 7047 | var h = imgData.height; |
michael@0 | 7048 | |
michael@0 | 7049 | var tmpCanvas = CachedCanvases.getCanvas('inlineImage', w, h); |
michael@0 | 7050 | var tmpCtx = tmpCanvas.context; |
michael@0 | 7051 | putBinaryImageData(tmpCtx, imgData); |
michael@0 | 7052 | |
michael@0 | 7053 | for (var i = 0, ii = map.length; i < ii; i++) { |
michael@0 | 7054 | var entry = map[i]; |
michael@0 | 7055 | ctx.save(); |
michael@0 | 7056 | ctx.transform.apply(ctx, entry.transform); |
michael@0 | 7057 | ctx.scale(1, -1); |
michael@0 | 7058 | ctx.drawImage(tmpCanvas.canvas, entry.x, entry.y, entry.w, entry.h, |
michael@0 | 7059 | 0, -1, 1, 1); |
michael@0 | 7060 | if (this.imageLayer) { |
michael@0 | 7061 | var position = this.getCanvasPosition(entry.x, entry.y); |
michael@0 | 7062 | this.imageLayer.appendImage({ |
michael@0 | 7063 | imgData: imgData, |
michael@0 | 7064 | left: position[0], |
michael@0 | 7065 | top: position[1], |
michael@0 | 7066 | width: w, |
michael@0 | 7067 | height: h |
michael@0 | 7068 | }); |
michael@0 | 7069 | } |
michael@0 | 7070 | ctx.restore(); |
michael@0 | 7071 | } |
michael@0 | 7072 | }, |
michael@0 | 7073 | |
michael@0 | 7074 | paintSolidColorImageMask: |
michael@0 | 7075 | function CanvasGraphics_paintSolidColorImageMask() { |
michael@0 | 7076 | this.ctx.fillRect(0, 0, 1, 1); |
michael@0 | 7077 | }, |
michael@0 | 7078 | |
michael@0 | 7079 | // Marked content |
michael@0 | 7080 | |
michael@0 | 7081 | markPoint: function CanvasGraphics_markPoint(tag) { |
michael@0 | 7082 | // TODO Marked content. |
michael@0 | 7083 | }, |
michael@0 | 7084 | markPointProps: function CanvasGraphics_markPointProps(tag, properties) { |
michael@0 | 7085 | // TODO Marked content. |
michael@0 | 7086 | }, |
michael@0 | 7087 | beginMarkedContent: function CanvasGraphics_beginMarkedContent(tag) { |
michael@0 | 7088 | // TODO Marked content. |
michael@0 | 7089 | }, |
michael@0 | 7090 | beginMarkedContentProps: function CanvasGraphics_beginMarkedContentProps( |
michael@0 | 7091 | tag, properties) { |
michael@0 | 7092 | // TODO Marked content. |
michael@0 | 7093 | }, |
michael@0 | 7094 | endMarkedContent: function CanvasGraphics_endMarkedContent() { |
michael@0 | 7095 | // TODO Marked content. |
michael@0 | 7096 | }, |
michael@0 | 7097 | |
michael@0 | 7098 | // Compatibility |
michael@0 | 7099 | |
michael@0 | 7100 | beginCompat: function CanvasGraphics_beginCompat() { |
michael@0 | 7101 | // TODO ignore undefined operators (should we do that anyway?) |
michael@0 | 7102 | }, |
michael@0 | 7103 | endCompat: function CanvasGraphics_endCompat() { |
michael@0 | 7104 | // TODO stop ignoring undefined operators |
michael@0 | 7105 | }, |
michael@0 | 7106 | |
michael@0 | 7107 | // Helper functions |
michael@0 | 7108 | |
michael@0 | 7109 | consumePath: function CanvasGraphics_consumePath() { |
michael@0 | 7110 | if (this.pendingClip) { |
michael@0 | 7111 | if (this.pendingClip == EO_CLIP) { |
michael@0 | 7112 | if ('mozFillRule' in this.ctx) { |
michael@0 | 7113 | this.ctx.mozFillRule = 'evenodd'; |
michael@0 | 7114 | this.ctx.clip(); |
michael@0 | 7115 | this.ctx.mozFillRule = 'nonzero'; |
michael@0 | 7116 | } else { |
michael@0 | 7117 | try { |
michael@0 | 7118 | this.ctx.clip('evenodd'); |
michael@0 | 7119 | } catch (ex) { |
michael@0 | 7120 | // shouldn't really happen, but browsers might think differently |
michael@0 | 7121 | this.ctx.clip(); |
michael@0 | 7122 | } |
michael@0 | 7123 | } |
michael@0 | 7124 | } else { |
michael@0 | 7125 | this.ctx.clip(); |
michael@0 | 7126 | } |
michael@0 | 7127 | this.pendingClip = null; |
michael@0 | 7128 | } |
michael@0 | 7129 | this.ctx.beginPath(); |
michael@0 | 7130 | }, |
michael@0 | 7131 | getSinglePixelWidth: function CanvasGraphics_getSinglePixelWidth(scale) { |
michael@0 | 7132 | var inverse = this.ctx.mozCurrentTransformInverse; |
michael@0 | 7133 | // max of the current horizontal and vertical scale |
michael@0 | 7134 | return Math.sqrt(Math.max( |
michael@0 | 7135 | (inverse[0] * inverse[0] + inverse[1] * inverse[1]), |
michael@0 | 7136 | (inverse[2] * inverse[2] + inverse[3] * inverse[3]))); |
michael@0 | 7137 | }, |
michael@0 | 7138 | getCanvasPosition: function CanvasGraphics_getCanvasPosition(x, y) { |
michael@0 | 7139 | var transform = this.ctx.mozCurrentTransform; |
michael@0 | 7140 | return [ |
michael@0 | 7141 | transform[0] * x + transform[2] * y + transform[4], |
michael@0 | 7142 | transform[1] * x + transform[3] * y + transform[5] |
michael@0 | 7143 | ]; |
michael@0 | 7144 | } |
michael@0 | 7145 | }; |
michael@0 | 7146 | |
michael@0 | 7147 | for (var op in OPS) { |
michael@0 | 7148 | CanvasGraphics.prototype[OPS[op]] = CanvasGraphics.prototype[op]; |
michael@0 | 7149 | } |
michael@0 | 7150 | |
michael@0 | 7151 | return CanvasGraphics; |
michael@0 | 7152 | })(); |
michael@0 | 7153 | |
michael@0 | 7154 | |
michael@0 | 7155 | |
michael@0 | 7156 | var WebGLUtils = (function WebGLUtilsClosure() { |
michael@0 | 7157 | function loadShader(gl, code, shaderType) { |
michael@0 | 7158 | var shader = gl.createShader(shaderType); |
michael@0 | 7159 | gl.shaderSource(shader, code); |
michael@0 | 7160 | gl.compileShader(shader); |
michael@0 | 7161 | var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS); |
michael@0 | 7162 | if (!compiled) { |
michael@0 | 7163 | var errorMsg = gl.getShaderInfoLog(shader); |
michael@0 | 7164 | throw new Error('Error during shader compilation: ' + errorMsg); |
michael@0 | 7165 | } |
michael@0 | 7166 | return shader; |
michael@0 | 7167 | } |
michael@0 | 7168 | function createVertexShader(gl, code) { |
michael@0 | 7169 | return loadShader(gl, code, gl.VERTEX_SHADER); |
michael@0 | 7170 | } |
michael@0 | 7171 | function createFragmentShader(gl, code) { |
michael@0 | 7172 | return loadShader(gl, code, gl.FRAGMENT_SHADER); |
michael@0 | 7173 | } |
michael@0 | 7174 | function createProgram(gl, shaders) { |
michael@0 | 7175 | var program = gl.createProgram(); |
michael@0 | 7176 | for (var i = 0, ii = shaders.length; i < ii; ++i) { |
michael@0 | 7177 | gl.attachShader(program, shaders[i]); |
michael@0 | 7178 | } |
michael@0 | 7179 | gl.linkProgram(program); |
michael@0 | 7180 | var linked = gl.getProgramParameter(program, gl.LINK_STATUS); |
michael@0 | 7181 | if (!linked) { |
michael@0 | 7182 | var errorMsg = gl.getProgramInfoLog(program); |
michael@0 | 7183 | throw new Error('Error during program linking: ' + errorMsg); |
michael@0 | 7184 | } |
michael@0 | 7185 | return program; |
michael@0 | 7186 | } |
michael@0 | 7187 | function createTexture(gl, image, textureId) { |
michael@0 | 7188 | gl.activeTexture(textureId); |
michael@0 | 7189 | var texture = gl.createTexture(); |
michael@0 | 7190 | gl.bindTexture(gl.TEXTURE_2D, texture); |
michael@0 | 7191 | |
michael@0 | 7192 | // Set the parameters so we can render any size image. |
michael@0 | 7193 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); |
michael@0 | 7194 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); |
michael@0 | 7195 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); |
michael@0 | 7196 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); |
michael@0 | 7197 | |
michael@0 | 7198 | // Upload the image into the texture. |
michael@0 | 7199 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); |
michael@0 | 7200 | return texture; |
michael@0 | 7201 | } |
michael@0 | 7202 | |
michael@0 | 7203 | var currentGL, currentCanvas; |
michael@0 | 7204 | function generageGL() { |
michael@0 | 7205 | if (currentGL) { |
michael@0 | 7206 | return; |
michael@0 | 7207 | } |
michael@0 | 7208 | currentCanvas = document.createElement('canvas'); |
michael@0 | 7209 | currentGL = currentCanvas.getContext('webgl', |
michael@0 | 7210 | { premultipliedalpha: false }); |
michael@0 | 7211 | } |
michael@0 | 7212 | |
michael@0 | 7213 | var smaskVertexShaderCode = '\ |
michael@0 | 7214 | attribute vec2 a_position; \ |
michael@0 | 7215 | attribute vec2 a_texCoord; \ |
michael@0 | 7216 | \ |
michael@0 | 7217 | uniform vec2 u_resolution; \ |
michael@0 | 7218 | \ |
michael@0 | 7219 | varying vec2 v_texCoord; \ |
michael@0 | 7220 | \ |
michael@0 | 7221 | void main() { \ |
michael@0 | 7222 | vec2 clipSpace = (a_position / u_resolution) * 2.0 - 1.0; \ |
michael@0 | 7223 | gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1); \ |
michael@0 | 7224 | \ |
michael@0 | 7225 | v_texCoord = a_texCoord; \ |
michael@0 | 7226 | } '; |
michael@0 | 7227 | |
michael@0 | 7228 | var smaskFragmentShaderCode = '\ |
michael@0 | 7229 | precision mediump float; \ |
michael@0 | 7230 | \ |
michael@0 | 7231 | uniform vec4 u_backdrop; \ |
michael@0 | 7232 | uniform int u_subtype; \ |
michael@0 | 7233 | uniform sampler2D u_image; \ |
michael@0 | 7234 | uniform sampler2D u_mask; \ |
michael@0 | 7235 | \ |
michael@0 | 7236 | varying vec2 v_texCoord; \ |
michael@0 | 7237 | \ |
michael@0 | 7238 | void main() { \ |
michael@0 | 7239 | vec4 imageColor = texture2D(u_image, v_texCoord); \ |
michael@0 | 7240 | vec4 maskColor = texture2D(u_mask, v_texCoord); \ |
michael@0 | 7241 | if (u_backdrop.a > 0.0) { \ |
michael@0 | 7242 | maskColor.rgb = maskColor.rgb * maskColor.a + \ |
michael@0 | 7243 | u_backdrop.rgb * (1.0 - maskColor.a); \ |
michael@0 | 7244 | } \ |
michael@0 | 7245 | float lum; \ |
michael@0 | 7246 | if (u_subtype == 0) { \ |
michael@0 | 7247 | lum = maskColor.a; \ |
michael@0 | 7248 | } else { \ |
michael@0 | 7249 | lum = maskColor.r * 0.3 + maskColor.g * 0.59 + \ |
michael@0 | 7250 | maskColor.b * 0.11; \ |
michael@0 | 7251 | } \ |
michael@0 | 7252 | imageColor.a *= lum; \ |
michael@0 | 7253 | imageColor.rgb *= imageColor.a; \ |
michael@0 | 7254 | gl_FragColor = imageColor; \ |
michael@0 | 7255 | } '; |
michael@0 | 7256 | |
michael@0 | 7257 | var smaskCache = null; |
michael@0 | 7258 | |
michael@0 | 7259 | function initSmaskGL() { |
michael@0 | 7260 | var canvas, gl; |
michael@0 | 7261 | |
michael@0 | 7262 | generageGL(); |
michael@0 | 7263 | canvas = currentCanvas; |
michael@0 | 7264 | currentCanvas = null; |
michael@0 | 7265 | gl = currentGL; |
michael@0 | 7266 | currentGL = null; |
michael@0 | 7267 | |
michael@0 | 7268 | // setup a GLSL program |
michael@0 | 7269 | var vertexShader = createVertexShader(gl, smaskVertexShaderCode); |
michael@0 | 7270 | var fragmentShader = createFragmentShader(gl, smaskFragmentShaderCode); |
michael@0 | 7271 | var program = createProgram(gl, [vertexShader, fragmentShader]); |
michael@0 | 7272 | gl.useProgram(program); |
michael@0 | 7273 | |
michael@0 | 7274 | var cache = {}; |
michael@0 | 7275 | cache.gl = gl; |
michael@0 | 7276 | cache.canvas = canvas; |
michael@0 | 7277 | cache.resolutionLocation = gl.getUniformLocation(program, 'u_resolution'); |
michael@0 | 7278 | cache.positionLocation = gl.getAttribLocation(program, 'a_position'); |
michael@0 | 7279 | cache.backdropLocation = gl.getUniformLocation(program, 'u_backdrop'); |
michael@0 | 7280 | cache.subtypeLocation = gl.getUniformLocation(program, 'u_subtype'); |
michael@0 | 7281 | |
michael@0 | 7282 | var texCoordLocation = gl.getAttribLocation(program, 'a_texCoord'); |
michael@0 | 7283 | var texLayerLocation = gl.getUniformLocation(program, 'u_image'); |
michael@0 | 7284 | var texMaskLocation = gl.getUniformLocation(program, 'u_mask'); |
michael@0 | 7285 | |
michael@0 | 7286 | // provide texture coordinates for the rectangle. |
michael@0 | 7287 | var texCoordBuffer = gl.createBuffer(); |
michael@0 | 7288 | gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer); |
michael@0 | 7289 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ |
michael@0 | 7290 | 0.0, 0.0, |
michael@0 | 7291 | 1.0, 0.0, |
michael@0 | 7292 | 0.0, 1.0, |
michael@0 | 7293 | 0.0, 1.0, |
michael@0 | 7294 | 1.0, 0.0, |
michael@0 | 7295 | 1.0, 1.0]), gl.STATIC_DRAW); |
michael@0 | 7296 | gl.enableVertexAttribArray(texCoordLocation); |
michael@0 | 7297 | gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0); |
michael@0 | 7298 | |
michael@0 | 7299 | gl.uniform1i(texLayerLocation, 0); |
michael@0 | 7300 | gl.uniform1i(texMaskLocation, 1); |
michael@0 | 7301 | |
michael@0 | 7302 | smaskCache = cache; |
michael@0 | 7303 | } |
michael@0 | 7304 | |
michael@0 | 7305 | function composeSMask(layer, mask, properties) { |
michael@0 | 7306 | var width = layer.width, height = layer.height; |
michael@0 | 7307 | |
michael@0 | 7308 | if (!smaskCache) { |
michael@0 | 7309 | initSmaskGL(); |
michael@0 | 7310 | } |
michael@0 | 7311 | var cache = smaskCache,canvas = cache.canvas, gl = cache.gl; |
michael@0 | 7312 | canvas.width = width; |
michael@0 | 7313 | canvas.height = height; |
michael@0 | 7314 | gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); |
michael@0 | 7315 | gl.uniform2f(cache.resolutionLocation, width, height); |
michael@0 | 7316 | |
michael@0 | 7317 | if (properties.backdrop) { |
michael@0 | 7318 | gl.uniform4f(cache.resolutionLocation, properties.backdrop[0], |
michael@0 | 7319 | properties.backdrop[1], properties.backdrop[2], 1); |
michael@0 | 7320 | } else { |
michael@0 | 7321 | gl.uniform4f(cache.resolutionLocation, 0, 0, 0, 0); |
michael@0 | 7322 | } |
michael@0 | 7323 | gl.uniform1i(cache.subtypeLocation, |
michael@0 | 7324 | properties.subtype === 'Luminosity' ? 1 : 0); |
michael@0 | 7325 | |
michael@0 | 7326 | // Create a textures |
michael@0 | 7327 | var texture = createTexture(gl, layer, gl.TEXTURE0); |
michael@0 | 7328 | var maskTexture = createTexture(gl, mask, gl.TEXTURE1); |
michael@0 | 7329 | |
michael@0 | 7330 | |
michael@0 | 7331 | // Create a buffer and put a single clipspace rectangle in |
michael@0 | 7332 | // it (2 triangles) |
michael@0 | 7333 | var buffer = gl.createBuffer(); |
michael@0 | 7334 | gl.bindBuffer(gl.ARRAY_BUFFER, buffer); |
michael@0 | 7335 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ |
michael@0 | 7336 | 0, 0, |
michael@0 | 7337 | width, 0, |
michael@0 | 7338 | 0, height, |
michael@0 | 7339 | 0, height, |
michael@0 | 7340 | width, 0, |
michael@0 | 7341 | width, height]), gl.STATIC_DRAW); |
michael@0 | 7342 | gl.enableVertexAttribArray(cache.positionLocation); |
michael@0 | 7343 | gl.vertexAttribPointer(cache.positionLocation, 2, gl.FLOAT, false, 0, 0); |
michael@0 | 7344 | |
michael@0 | 7345 | // draw |
michael@0 | 7346 | gl.clearColor(0, 0, 0, 0); |
michael@0 | 7347 | gl.enable(gl.BLEND); |
michael@0 | 7348 | gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); |
michael@0 | 7349 | gl.clear(gl.COLOR_BUFFER_BIT); |
michael@0 | 7350 | |
michael@0 | 7351 | gl.drawArrays(gl.TRIANGLES, 0, 6); |
michael@0 | 7352 | |
michael@0 | 7353 | gl.flush(); |
michael@0 | 7354 | |
michael@0 | 7355 | gl.deleteTexture(texture); |
michael@0 | 7356 | gl.deleteTexture(maskTexture); |
michael@0 | 7357 | gl.deleteBuffer(buffer); |
michael@0 | 7358 | |
michael@0 | 7359 | return canvas; |
michael@0 | 7360 | } |
michael@0 | 7361 | |
michael@0 | 7362 | var figuresVertexShaderCode = '\ |
michael@0 | 7363 | attribute vec2 a_position; \ |
michael@0 | 7364 | attribute vec3 a_color; \ |
michael@0 | 7365 | \ |
michael@0 | 7366 | uniform vec2 u_resolution; \ |
michael@0 | 7367 | uniform vec2 u_scale; \ |
michael@0 | 7368 | uniform vec2 u_offset; \ |
michael@0 | 7369 | \ |
michael@0 | 7370 | varying vec4 v_color; \ |
michael@0 | 7371 | \ |
michael@0 | 7372 | void main() { \ |
michael@0 | 7373 | vec2 position = (a_position + u_offset) * u_scale; \ |
michael@0 | 7374 | vec2 clipSpace = (position / u_resolution) * 2.0 - 1.0; \ |
michael@0 | 7375 | gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1); \ |
michael@0 | 7376 | \ |
michael@0 | 7377 | v_color = vec4(a_color / 255.0, 1.0); \ |
michael@0 | 7378 | } '; |
michael@0 | 7379 | |
michael@0 | 7380 | var figuresFragmentShaderCode = '\ |
michael@0 | 7381 | precision mediump float; \ |
michael@0 | 7382 | \ |
michael@0 | 7383 | varying vec4 v_color; \ |
michael@0 | 7384 | \ |
michael@0 | 7385 | void main() { \ |
michael@0 | 7386 | gl_FragColor = v_color; \ |
michael@0 | 7387 | } '; |
michael@0 | 7388 | |
michael@0 | 7389 | var figuresCache = null; |
michael@0 | 7390 | |
michael@0 | 7391 | function initFiguresGL() { |
michael@0 | 7392 | var canvas, gl; |
michael@0 | 7393 | |
michael@0 | 7394 | generageGL(); |
michael@0 | 7395 | canvas = currentCanvas; |
michael@0 | 7396 | currentCanvas = null; |
michael@0 | 7397 | gl = currentGL; |
michael@0 | 7398 | currentGL = null; |
michael@0 | 7399 | |
michael@0 | 7400 | // setup a GLSL program |
michael@0 | 7401 | var vertexShader = createVertexShader(gl, figuresVertexShaderCode); |
michael@0 | 7402 | var fragmentShader = createFragmentShader(gl, figuresFragmentShaderCode); |
michael@0 | 7403 | var program = createProgram(gl, [vertexShader, fragmentShader]); |
michael@0 | 7404 | gl.useProgram(program); |
michael@0 | 7405 | |
michael@0 | 7406 | var cache = {}; |
michael@0 | 7407 | cache.gl = gl; |
michael@0 | 7408 | cache.canvas = canvas; |
michael@0 | 7409 | cache.resolutionLocation = gl.getUniformLocation(program, 'u_resolution'); |
michael@0 | 7410 | cache.scaleLocation = gl.getUniformLocation(program, 'u_scale'); |
michael@0 | 7411 | cache.offsetLocation = gl.getUniformLocation(program, 'u_offset'); |
michael@0 | 7412 | cache.positionLocation = gl.getAttribLocation(program, 'a_position'); |
michael@0 | 7413 | cache.colorLocation = gl.getAttribLocation(program, 'a_color'); |
michael@0 | 7414 | |
michael@0 | 7415 | figuresCache = cache; |
michael@0 | 7416 | } |
michael@0 | 7417 | |
michael@0 | 7418 | function drawFigures(width, height, backgroundColor, figures, context) { |
michael@0 | 7419 | if (!figuresCache) { |
michael@0 | 7420 | initFiguresGL(); |
michael@0 | 7421 | } |
michael@0 | 7422 | var cache = figuresCache, canvas = cache.canvas, gl = cache.gl; |
michael@0 | 7423 | |
michael@0 | 7424 | canvas.width = width; |
michael@0 | 7425 | canvas.height = height; |
michael@0 | 7426 | gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); |
michael@0 | 7427 | gl.uniform2f(cache.resolutionLocation, width, height); |
michael@0 | 7428 | |
michael@0 | 7429 | // count triangle points |
michael@0 | 7430 | var count = 0; |
michael@0 | 7431 | var i, ii, rows; |
michael@0 | 7432 | for (i = 0, ii = figures.length; i < ii; i++) { |
michael@0 | 7433 | switch (figures[i].type) { |
michael@0 | 7434 | case 'lattice': |
michael@0 | 7435 | rows = (figures[i].coords.length / figures[i].verticesPerRow) | 0; |
michael@0 | 7436 | count += (rows - 1) * (figures[i].verticesPerRow - 1) * 6; |
michael@0 | 7437 | break; |
michael@0 | 7438 | case 'triangles': |
michael@0 | 7439 | count += figures[i].coords.length; |
michael@0 | 7440 | break; |
michael@0 | 7441 | } |
michael@0 | 7442 | } |
michael@0 | 7443 | // transfer data |
michael@0 | 7444 | var coords = new Float32Array(count * 2); |
michael@0 | 7445 | var colors = new Uint8Array(count * 3); |
michael@0 | 7446 | var coordsMap = context.coords, colorsMap = context.colors; |
michael@0 | 7447 | var pIndex = 0, cIndex = 0; |
michael@0 | 7448 | for (i = 0, ii = figures.length; i < ii; i++) { |
michael@0 | 7449 | var figure = figures[i], ps = figure.coords, cs = figure.colors; |
michael@0 | 7450 | switch (figure.type) { |
michael@0 | 7451 | case 'lattice': |
michael@0 | 7452 | var cols = figure.verticesPerRow; |
michael@0 | 7453 | rows = (ps.length / cols) | 0; |
michael@0 | 7454 | for (var row = 1; row < rows; row++) { |
michael@0 | 7455 | var offset = row * cols + 1; |
michael@0 | 7456 | for (var col = 1; col < cols; col++, offset++) { |
michael@0 | 7457 | coords[pIndex] = coordsMap[ps[offset - cols - 1]]; |
michael@0 | 7458 | coords[pIndex + 1] = coordsMap[ps[offset - cols - 1] + 1]; |
michael@0 | 7459 | coords[pIndex + 2] = coordsMap[ps[offset - cols]]; |
michael@0 | 7460 | coords[pIndex + 3] = coordsMap[ps[offset - cols] + 1]; |
michael@0 | 7461 | coords[pIndex + 4] = coordsMap[ps[offset - 1]]; |
michael@0 | 7462 | coords[pIndex + 5] = coordsMap[ps[offset - 1] + 1]; |
michael@0 | 7463 | colors[cIndex] = colorsMap[cs[offset - cols - 1]]; |
michael@0 | 7464 | colors[cIndex + 1] = colorsMap[cs[offset - cols - 1] + 1]; |
michael@0 | 7465 | colors[cIndex + 2] = colorsMap[cs[offset - cols - 1] + 2]; |
michael@0 | 7466 | colors[cIndex + 3] = colorsMap[cs[offset - cols]]; |
michael@0 | 7467 | colors[cIndex + 4] = colorsMap[cs[offset - cols] + 1]; |
michael@0 | 7468 | colors[cIndex + 5] = colorsMap[cs[offset - cols] + 2]; |
michael@0 | 7469 | colors[cIndex + 6] = colorsMap[cs[offset - 1]]; |
michael@0 | 7470 | colors[cIndex + 7] = colorsMap[cs[offset - 1] + 1]; |
michael@0 | 7471 | colors[cIndex + 8] = colorsMap[cs[offset - 1] + 2]; |
michael@0 | 7472 | |
michael@0 | 7473 | coords[pIndex + 6] = coords[pIndex + 2]; |
michael@0 | 7474 | coords[pIndex + 7] = coords[pIndex + 3]; |
michael@0 | 7475 | coords[pIndex + 8] = coords[pIndex + 4]; |
michael@0 | 7476 | coords[pIndex + 9] = coords[pIndex + 5]; |
michael@0 | 7477 | coords[pIndex + 10] = coordsMap[ps[offset]]; |
michael@0 | 7478 | coords[pIndex + 11] = coordsMap[ps[offset] + 1]; |
michael@0 | 7479 | colors[cIndex + 9] = colors[cIndex + 3]; |
michael@0 | 7480 | colors[cIndex + 10] = colors[cIndex + 4]; |
michael@0 | 7481 | colors[cIndex + 11] = colors[cIndex + 5]; |
michael@0 | 7482 | colors[cIndex + 12] = colors[cIndex + 6]; |
michael@0 | 7483 | colors[cIndex + 13] = colors[cIndex + 7]; |
michael@0 | 7484 | colors[cIndex + 14] = colors[cIndex + 8]; |
michael@0 | 7485 | colors[cIndex + 15] = colorsMap[cs[offset]]; |
michael@0 | 7486 | colors[cIndex + 16] = colorsMap[cs[offset] + 1]; |
michael@0 | 7487 | colors[cIndex + 17] = colorsMap[cs[offset] + 2]; |
michael@0 | 7488 | pIndex += 12; |
michael@0 | 7489 | cIndex += 18; |
michael@0 | 7490 | } |
michael@0 | 7491 | } |
michael@0 | 7492 | break; |
michael@0 | 7493 | case 'triangles': |
michael@0 | 7494 | for (var j = 0, jj = ps.length; j < jj; j++) { |
michael@0 | 7495 | coords[pIndex] = coordsMap[ps[j]]; |
michael@0 | 7496 | coords[pIndex + 1] = coordsMap[ps[j] + 1]; |
michael@0 | 7497 | colors[cIndex] = colorsMap[cs[i]]; |
michael@0 | 7498 | colors[cIndex + 1] = colorsMap[cs[j] + 1]; |
michael@0 | 7499 | colors[cIndex + 2] = colorsMap[cs[j] + 2]; |
michael@0 | 7500 | pIndex += 2; |
michael@0 | 7501 | cIndex += 3; |
michael@0 | 7502 | } |
michael@0 | 7503 | break; |
michael@0 | 7504 | } |
michael@0 | 7505 | } |
michael@0 | 7506 | |
michael@0 | 7507 | // draw |
michael@0 | 7508 | if (backgroundColor) { |
michael@0 | 7509 | gl.clearColor(backgroundColor[0] / 255, backgroundColor[1] / 255, |
michael@0 | 7510 | backgroundColor[2] / 255, 1.0); |
michael@0 | 7511 | } else { |
michael@0 | 7512 | gl.clearColor(0, 0, 0, 0); |
michael@0 | 7513 | } |
michael@0 | 7514 | gl.clear(gl.COLOR_BUFFER_BIT); |
michael@0 | 7515 | |
michael@0 | 7516 | var coordsBuffer = gl.createBuffer(); |
michael@0 | 7517 | gl.bindBuffer(gl.ARRAY_BUFFER, coordsBuffer); |
michael@0 | 7518 | gl.bufferData(gl.ARRAY_BUFFER, coords, gl.STATIC_DRAW); |
michael@0 | 7519 | gl.enableVertexAttribArray(cache.positionLocation); |
michael@0 | 7520 | gl.vertexAttribPointer(cache.positionLocation, 2, gl.FLOAT, false, 0, 0); |
michael@0 | 7521 | |
michael@0 | 7522 | var colorsBuffer = gl.createBuffer(); |
michael@0 | 7523 | gl.bindBuffer(gl.ARRAY_BUFFER, colorsBuffer); |
michael@0 | 7524 | gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW); |
michael@0 | 7525 | gl.enableVertexAttribArray(cache.colorLocation); |
michael@0 | 7526 | gl.vertexAttribPointer(cache.colorLocation, 3, gl.UNSIGNED_BYTE, false, |
michael@0 | 7527 | 0, 0); |
michael@0 | 7528 | |
michael@0 | 7529 | gl.uniform2f(cache.scaleLocation, context.scaleX, context.scaleY); |
michael@0 | 7530 | gl.uniform2f(cache.offsetLocation, context.offsetX, context.offsetY); |
michael@0 | 7531 | |
michael@0 | 7532 | gl.drawArrays(gl.TRIANGLES, 0, count); |
michael@0 | 7533 | |
michael@0 | 7534 | gl.flush(); |
michael@0 | 7535 | |
michael@0 | 7536 | gl.deleteBuffer(coordsBuffer); |
michael@0 | 7537 | gl.deleteBuffer(colorsBuffer); |
michael@0 | 7538 | |
michael@0 | 7539 | return canvas; |
michael@0 | 7540 | } |
michael@0 | 7541 | |
michael@0 | 7542 | function cleanup() { |
michael@0 | 7543 | smaskCache = null; |
michael@0 | 7544 | figuresCache = null; |
michael@0 | 7545 | } |
michael@0 | 7546 | |
michael@0 | 7547 | return { |
michael@0 | 7548 | get isEnabled() { |
michael@0 | 7549 | if (PDFJS.disableWebGL) { |
michael@0 | 7550 | return false; |
michael@0 | 7551 | } |
michael@0 | 7552 | var enabled = false; |
michael@0 | 7553 | try { |
michael@0 | 7554 | generageGL(); |
michael@0 | 7555 | enabled = !!currentGL; |
michael@0 | 7556 | } catch (e) { } |
michael@0 | 7557 | return shadow(this, 'isEnabled', enabled); |
michael@0 | 7558 | }, |
michael@0 | 7559 | composeSMask: composeSMask, |
michael@0 | 7560 | drawFigures: drawFigures, |
michael@0 | 7561 | clear: cleanup |
michael@0 | 7562 | }; |
michael@0 | 7563 | })(); |
michael@0 | 7564 | |
michael@0 | 7565 | |
michael@0 | 7566 | var ShadingIRs = {}; |
michael@0 | 7567 | |
michael@0 | 7568 | ShadingIRs.RadialAxial = { |
michael@0 | 7569 | fromIR: function RadialAxial_fromIR(raw) { |
michael@0 | 7570 | var type = raw[1]; |
michael@0 | 7571 | var colorStops = raw[2]; |
michael@0 | 7572 | var p0 = raw[3]; |
michael@0 | 7573 | var p1 = raw[4]; |
michael@0 | 7574 | var r0 = raw[5]; |
michael@0 | 7575 | var r1 = raw[6]; |
michael@0 | 7576 | return { |
michael@0 | 7577 | type: 'Pattern', |
michael@0 | 7578 | getPattern: function RadialAxial_getPattern(ctx) { |
michael@0 | 7579 | var grad; |
michael@0 | 7580 | if (type === 'axial') { |
michael@0 | 7581 | grad = ctx.createLinearGradient(p0[0], p0[1], p1[0], p1[1]); |
michael@0 | 7582 | } else if (type === 'radial') { |
michael@0 | 7583 | grad = ctx.createRadialGradient(p0[0], p0[1], r0, p1[0], p1[1], r1); |
michael@0 | 7584 | } |
michael@0 | 7585 | |
michael@0 | 7586 | for (var i = 0, ii = colorStops.length; i < ii; ++i) { |
michael@0 | 7587 | var c = colorStops[i]; |
michael@0 | 7588 | grad.addColorStop(c[0], c[1]); |
michael@0 | 7589 | } |
michael@0 | 7590 | return grad; |
michael@0 | 7591 | } |
michael@0 | 7592 | }; |
michael@0 | 7593 | } |
michael@0 | 7594 | }; |
michael@0 | 7595 | |
michael@0 | 7596 | var createMeshCanvas = (function createMeshCanvasClosure() { |
michael@0 | 7597 | function drawTriangle(data, context, p1, p2, p3, c1, c2, c3) { |
michael@0 | 7598 | // Very basic Gouraud-shaded triangle rasterization algorithm. |
michael@0 | 7599 | var coords = context.coords, colors = context.colors; |
michael@0 | 7600 | var bytes = data.data, rowSize = data.width * 4; |
michael@0 | 7601 | var tmp; |
michael@0 | 7602 | if (coords[p1 + 1] > coords[p2 + 1]) { |
michael@0 | 7603 | tmp = p1; p1 = p2; p2 = tmp; tmp = c1; c1 = c2; c2 = tmp; |
michael@0 | 7604 | } |
michael@0 | 7605 | if (coords[p2 + 1] > coords[p3 + 1]) { |
michael@0 | 7606 | tmp = p2; p2 = p3; p3 = tmp; tmp = c2; c2 = c3; c3 = tmp; |
michael@0 | 7607 | } |
michael@0 | 7608 | if (coords[p1 + 1] > coords[p2 + 1]) { |
michael@0 | 7609 | tmp = p1; p1 = p2; p2 = tmp; tmp = c1; c1 = c2; c2 = tmp; |
michael@0 | 7610 | } |
michael@0 | 7611 | var x1 = (coords[p1] + context.offsetX) * context.scaleX; |
michael@0 | 7612 | var y1 = (coords[p1 + 1] + context.offsetY) * context.scaleY; |
michael@0 | 7613 | var x2 = (coords[p2] + context.offsetX) * context.scaleX; |
michael@0 | 7614 | var y2 = (coords[p2 + 1] + context.offsetY) * context.scaleY; |
michael@0 | 7615 | var x3 = (coords[p3] + context.offsetX) * context.scaleX; |
michael@0 | 7616 | var y3 = (coords[p3 + 1] + context.offsetY) * context.scaleY; |
michael@0 | 7617 | if (y1 >= y3) { |
michael@0 | 7618 | return; |
michael@0 | 7619 | } |
michael@0 | 7620 | var c1r = colors[c1], c1g = colors[c1 + 1], c1b = colors[c1 + 2]; |
michael@0 | 7621 | var c2r = colors[c2], c2g = colors[c2 + 1], c2b = colors[c2 + 2]; |
michael@0 | 7622 | var c3r = colors[c3], c3g = colors[c3 + 1], c3b = colors[c3 + 2]; |
michael@0 | 7623 | |
michael@0 | 7624 | var minY = Math.round(y1), maxY = Math.round(y3); |
michael@0 | 7625 | var xa, car, cag, cab; |
michael@0 | 7626 | var xb, cbr, cbg, cbb; |
michael@0 | 7627 | var k; |
michael@0 | 7628 | for (var y = minY; y <= maxY; y++) { |
michael@0 | 7629 | if (y < y2) { |
michael@0 | 7630 | k = y < y1 ? 0 : y1 === y2 ? 1 : (y1 - y) / (y1 - y2); |
michael@0 | 7631 | xa = x1 - (x1 - x2) * k; |
michael@0 | 7632 | car = c1r - (c1r - c2r) * k; |
michael@0 | 7633 | cag = c1g - (c1g - c2g) * k; |
michael@0 | 7634 | cab = c1b - (c1b - c2b) * k; |
michael@0 | 7635 | } else { |
michael@0 | 7636 | k = y > y3 ? 1 : y2 === y3 ? 0 : (y2 - y) / (y2 - y3); |
michael@0 | 7637 | xa = x2 - (x2 - x3) * k; |
michael@0 | 7638 | car = c2r - (c2r - c3r) * k; |
michael@0 | 7639 | cag = c2g - (c2g - c3g) * k; |
michael@0 | 7640 | cab = c2b - (c2b - c3b) * k; |
michael@0 | 7641 | } |
michael@0 | 7642 | k = y < y1 ? 0 : y > y3 ? 1 : (y1 - y) / (y1 - y3); |
michael@0 | 7643 | xb = x1 - (x1 - x3) * k; |
michael@0 | 7644 | cbr = c1r - (c1r - c3r) * k; |
michael@0 | 7645 | cbg = c1g - (c1g - c3g) * k; |
michael@0 | 7646 | cbb = c1b - (c1b - c3b) * k; |
michael@0 | 7647 | var x1_ = Math.round(Math.min(xa, xb)); |
michael@0 | 7648 | var x2_ = Math.round(Math.max(xa, xb)); |
michael@0 | 7649 | var j = rowSize * y + x1_ * 4; |
michael@0 | 7650 | for (var x = x1_; x <= x2_; x++) { |
michael@0 | 7651 | k = (xa - x) / (xa - xb); |
michael@0 | 7652 | k = k < 0 ? 0 : k > 1 ? 1 : k; |
michael@0 | 7653 | bytes[j++] = (car - (car - cbr) * k) | 0; |
michael@0 | 7654 | bytes[j++] = (cag - (cag - cbg) * k) | 0; |
michael@0 | 7655 | bytes[j++] = (cab - (cab - cbb) * k) | 0; |
michael@0 | 7656 | bytes[j++] = 255; |
michael@0 | 7657 | } |
michael@0 | 7658 | } |
michael@0 | 7659 | } |
michael@0 | 7660 | |
michael@0 | 7661 | function drawFigure(data, figure, context) { |
michael@0 | 7662 | var ps = figure.coords; |
michael@0 | 7663 | var cs = figure.colors; |
michael@0 | 7664 | var i, ii; |
michael@0 | 7665 | switch (figure.type) { |
michael@0 | 7666 | case 'lattice': |
michael@0 | 7667 | var verticesPerRow = figure.verticesPerRow; |
michael@0 | 7668 | var rows = Math.floor(ps.length / verticesPerRow) - 1; |
michael@0 | 7669 | var cols = verticesPerRow - 1; |
michael@0 | 7670 | for (i = 0; i < rows; i++) { |
michael@0 | 7671 | var q = i * verticesPerRow; |
michael@0 | 7672 | for (var j = 0; j < cols; j++, q++) { |
michael@0 | 7673 | drawTriangle(data, context, |
michael@0 | 7674 | ps[q], ps[q + 1], ps[q + verticesPerRow], |
michael@0 | 7675 | cs[q], cs[q + 1], cs[q + verticesPerRow]); |
michael@0 | 7676 | drawTriangle(data, context, |
michael@0 | 7677 | ps[q + verticesPerRow + 1], ps[q + 1], ps[q + verticesPerRow], |
michael@0 | 7678 | cs[q + verticesPerRow + 1], cs[q + 1], cs[q + verticesPerRow]); |
michael@0 | 7679 | } |
michael@0 | 7680 | } |
michael@0 | 7681 | break; |
michael@0 | 7682 | case 'triangles': |
michael@0 | 7683 | for (i = 0, ii = ps.length; i < ii; i += 3) { |
michael@0 | 7684 | drawTriangle(data, context, |
michael@0 | 7685 | ps[i], ps[i + 1], ps[i + 2], |
michael@0 | 7686 | cs[i], cs[i + 1], cs[i + 2]); |
michael@0 | 7687 | } |
michael@0 | 7688 | break; |
michael@0 | 7689 | default: |
michael@0 | 7690 | error('illigal figure'); |
michael@0 | 7691 | break; |
michael@0 | 7692 | } |
michael@0 | 7693 | } |
michael@0 | 7694 | |
michael@0 | 7695 | function createMeshCanvas(bounds, combinesScale, coords, colors, figures, |
michael@0 | 7696 | backgroundColor) { |
michael@0 | 7697 | // we will increase scale on some weird factor to let antialiasing take |
michael@0 | 7698 | // care of "rough" edges |
michael@0 | 7699 | var EXPECTED_SCALE = 1.1; |
michael@0 | 7700 | // MAX_PATTERN_SIZE is used to avoid OOM situation. |
michael@0 | 7701 | var MAX_PATTERN_SIZE = 3000; // 10in @ 300dpi shall be enough |
michael@0 | 7702 | |
michael@0 | 7703 | var offsetX = Math.floor(bounds[0]); |
michael@0 | 7704 | var offsetY = Math.floor(bounds[1]); |
michael@0 | 7705 | var boundsWidth = Math.ceil(bounds[2]) - offsetX; |
michael@0 | 7706 | var boundsHeight = Math.ceil(bounds[3]) - offsetY; |
michael@0 | 7707 | |
michael@0 | 7708 | var width = Math.min(Math.ceil(Math.abs(boundsWidth * combinesScale[0] * |
michael@0 | 7709 | EXPECTED_SCALE)), MAX_PATTERN_SIZE); |
michael@0 | 7710 | var height = Math.min(Math.ceil(Math.abs(boundsHeight * combinesScale[1] * |
michael@0 | 7711 | EXPECTED_SCALE)), MAX_PATTERN_SIZE); |
michael@0 | 7712 | var scaleX = boundsWidth / width; |
michael@0 | 7713 | var scaleY = boundsHeight / height; |
michael@0 | 7714 | |
michael@0 | 7715 | var context = { |
michael@0 | 7716 | coords: coords, |
michael@0 | 7717 | colors: colors, |
michael@0 | 7718 | offsetX: -offsetX, |
michael@0 | 7719 | offsetY: -offsetY, |
michael@0 | 7720 | scaleX: 1 / scaleX, |
michael@0 | 7721 | scaleY: 1 / scaleY |
michael@0 | 7722 | }; |
michael@0 | 7723 | |
michael@0 | 7724 | var canvas, tmpCanvas, i, ii; |
michael@0 | 7725 | if (WebGLUtils.isEnabled) { |
michael@0 | 7726 | canvas = WebGLUtils.drawFigures(width, height, backgroundColor, |
michael@0 | 7727 | figures, context); |
michael@0 | 7728 | |
michael@0 | 7729 | // https://bugzilla.mozilla.org/show_bug.cgi?id=972126 |
michael@0 | 7730 | tmpCanvas = CachedCanvases.getCanvas('mesh', width, height, false); |
michael@0 | 7731 | tmpCanvas.context.drawImage(canvas, 0, 0); |
michael@0 | 7732 | canvas = tmpCanvas.canvas; |
michael@0 | 7733 | } else { |
michael@0 | 7734 | tmpCanvas = CachedCanvases.getCanvas('mesh', width, height, false); |
michael@0 | 7735 | var tmpCtx = tmpCanvas.context; |
michael@0 | 7736 | |
michael@0 | 7737 | var data = tmpCtx.createImageData(width, height); |
michael@0 | 7738 | if (backgroundColor) { |
michael@0 | 7739 | var bytes = data.data; |
michael@0 | 7740 | for (i = 0, ii = bytes.length; i < ii; i += 4) { |
michael@0 | 7741 | bytes[i] = backgroundColor[0]; |
michael@0 | 7742 | bytes[i + 1] = backgroundColor[1]; |
michael@0 | 7743 | bytes[i + 2] = backgroundColor[2]; |
michael@0 | 7744 | bytes[i + 3] = 255; |
michael@0 | 7745 | } |
michael@0 | 7746 | } |
michael@0 | 7747 | for (i = 0; i < figures.length; i++) { |
michael@0 | 7748 | drawFigure(data, figures[i], context); |
michael@0 | 7749 | } |
michael@0 | 7750 | tmpCtx.putImageData(data, 0, 0); |
michael@0 | 7751 | canvas = tmpCanvas.canvas; |
michael@0 | 7752 | } |
michael@0 | 7753 | |
michael@0 | 7754 | return {canvas: canvas, offsetX: offsetX, offsetY: offsetY, |
michael@0 | 7755 | scaleX: scaleX, scaleY: scaleY}; |
michael@0 | 7756 | } |
michael@0 | 7757 | return createMeshCanvas; |
michael@0 | 7758 | })(); |
michael@0 | 7759 | |
michael@0 | 7760 | ShadingIRs.Mesh = { |
michael@0 | 7761 | fromIR: function Mesh_fromIR(raw) { |
michael@0 | 7762 | //var type = raw[1]; |
michael@0 | 7763 | var coords = raw[2]; |
michael@0 | 7764 | var colors = raw[3]; |
michael@0 | 7765 | var figures = raw[4]; |
michael@0 | 7766 | var bounds = raw[5]; |
michael@0 | 7767 | var matrix = raw[6]; |
michael@0 | 7768 | //var bbox = raw[7]; |
michael@0 | 7769 | var background = raw[8]; |
michael@0 | 7770 | return { |
michael@0 | 7771 | type: 'Pattern', |
michael@0 | 7772 | getPattern: function Mesh_getPattern(ctx, owner, shadingFill) { |
michael@0 | 7773 | var combinedScale; |
michael@0 | 7774 | // Obtain scale from matrix and current transformation matrix. |
michael@0 | 7775 | if (shadingFill) { |
michael@0 | 7776 | combinedScale = Util.singularValueDecompose2dScale( |
michael@0 | 7777 | ctx.mozCurrentTransform); |
michael@0 | 7778 | } else { |
michael@0 | 7779 | var matrixScale = Util.singularValueDecompose2dScale(matrix); |
michael@0 | 7780 | var curMatrixScale = Util.singularValueDecompose2dScale( |
michael@0 | 7781 | owner.baseTransform); |
michael@0 | 7782 | combinedScale = [matrixScale[0] * curMatrixScale[0], |
michael@0 | 7783 | matrixScale[1] * curMatrixScale[1]]; |
michael@0 | 7784 | } |
michael@0 | 7785 | |
michael@0 | 7786 | |
michael@0 | 7787 | // Rasterizing on the main thread since sending/queue large canvases |
michael@0 | 7788 | // might cause OOM. |
michael@0 | 7789 | var temporaryPatternCanvas = createMeshCanvas(bounds, combinedScale, |
michael@0 | 7790 | coords, colors, figures, shadingFill ? null : background); |
michael@0 | 7791 | |
michael@0 | 7792 | if (!shadingFill) { |
michael@0 | 7793 | ctx.setTransform.apply(ctx, owner.baseTransform); |
michael@0 | 7794 | if (matrix) { |
michael@0 | 7795 | ctx.transform.apply(ctx, matrix); |
michael@0 | 7796 | } |
michael@0 | 7797 | } |
michael@0 | 7798 | |
michael@0 | 7799 | ctx.translate(temporaryPatternCanvas.offsetX, |
michael@0 | 7800 | temporaryPatternCanvas.offsetY); |
michael@0 | 7801 | ctx.scale(temporaryPatternCanvas.scaleX, |
michael@0 | 7802 | temporaryPatternCanvas.scaleY); |
michael@0 | 7803 | |
michael@0 | 7804 | return ctx.createPattern(temporaryPatternCanvas.canvas, 'no-repeat'); |
michael@0 | 7805 | } |
michael@0 | 7806 | }; |
michael@0 | 7807 | } |
michael@0 | 7808 | }; |
michael@0 | 7809 | |
michael@0 | 7810 | ShadingIRs.Dummy = { |
michael@0 | 7811 | fromIR: function Dummy_fromIR() { |
michael@0 | 7812 | return { |
michael@0 | 7813 | type: 'Pattern', |
michael@0 | 7814 | getPattern: function Dummy_fromIR_getPattern() { |
michael@0 | 7815 | return 'hotpink'; |
michael@0 | 7816 | } |
michael@0 | 7817 | }; |
michael@0 | 7818 | } |
michael@0 | 7819 | }; |
michael@0 | 7820 | |
michael@0 | 7821 | function getShadingPatternFromIR(raw) { |
michael@0 | 7822 | var shadingIR = ShadingIRs[raw[0]]; |
michael@0 | 7823 | if (!shadingIR) { |
michael@0 | 7824 | error('Unknown IR type: ' + raw[0]); |
michael@0 | 7825 | } |
michael@0 | 7826 | return shadingIR.fromIR(raw); |
michael@0 | 7827 | } |
michael@0 | 7828 | |
michael@0 | 7829 | var TilingPattern = (function TilingPatternClosure() { |
michael@0 | 7830 | var PaintType = { |
michael@0 | 7831 | COLORED: 1, |
michael@0 | 7832 | UNCOLORED: 2 |
michael@0 | 7833 | }; |
michael@0 | 7834 | |
michael@0 | 7835 | var MAX_PATTERN_SIZE = 3000; // 10in @ 300dpi shall be enough |
michael@0 | 7836 | |
michael@0 | 7837 | function TilingPattern(IR, color, ctx, objs, commonObjs, baseTransform) { |
michael@0 | 7838 | this.name = IR[1][0].name; |
michael@0 | 7839 | this.operatorList = IR[2]; |
michael@0 | 7840 | this.matrix = IR[3] || [1, 0, 0, 1, 0, 0]; |
michael@0 | 7841 | this.bbox = IR[4]; |
michael@0 | 7842 | this.xstep = IR[5]; |
michael@0 | 7843 | this.ystep = IR[6]; |
michael@0 | 7844 | this.paintType = IR[7]; |
michael@0 | 7845 | this.tilingType = IR[8]; |
michael@0 | 7846 | this.color = color; |
michael@0 | 7847 | this.objs = objs; |
michael@0 | 7848 | this.commonObjs = commonObjs; |
michael@0 | 7849 | this.baseTransform = baseTransform; |
michael@0 | 7850 | this.type = 'Pattern'; |
michael@0 | 7851 | this.ctx = ctx; |
michael@0 | 7852 | } |
michael@0 | 7853 | |
michael@0 | 7854 | TilingPattern.prototype = { |
michael@0 | 7855 | createPatternCanvas: function TilinPattern_createPatternCanvas(owner) { |
michael@0 | 7856 | var operatorList = this.operatorList; |
michael@0 | 7857 | var bbox = this.bbox; |
michael@0 | 7858 | var xstep = this.xstep; |
michael@0 | 7859 | var ystep = this.ystep; |
michael@0 | 7860 | var paintType = this.paintType; |
michael@0 | 7861 | var tilingType = this.tilingType; |
michael@0 | 7862 | var color = this.color; |
michael@0 | 7863 | var objs = this.objs; |
michael@0 | 7864 | var commonObjs = this.commonObjs; |
michael@0 | 7865 | |
michael@0 | 7866 | info('TilingType: ' + tilingType); |
michael@0 | 7867 | |
michael@0 | 7868 | var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3]; |
michael@0 | 7869 | |
michael@0 | 7870 | var topLeft = [x0, y0]; |
michael@0 | 7871 | // we want the canvas to be as large as the step size |
michael@0 | 7872 | var botRight = [x0 + xstep, y0 + ystep]; |
michael@0 | 7873 | |
michael@0 | 7874 | var width = botRight[0] - topLeft[0]; |
michael@0 | 7875 | var height = botRight[1] - topLeft[1]; |
michael@0 | 7876 | |
michael@0 | 7877 | // Obtain scale from matrix and current transformation matrix. |
michael@0 | 7878 | var matrixScale = Util.singularValueDecompose2dScale(this.matrix); |
michael@0 | 7879 | var curMatrixScale = Util.singularValueDecompose2dScale( |
michael@0 | 7880 | this.baseTransform); |
michael@0 | 7881 | var combinedScale = [matrixScale[0] * curMatrixScale[0], |
michael@0 | 7882 | matrixScale[1] * curMatrixScale[1]]; |
michael@0 | 7883 | |
michael@0 | 7884 | // MAX_PATTERN_SIZE is used to avoid OOM situation. |
michael@0 | 7885 | // Use width and height values that are as close as possible to the end |
michael@0 | 7886 | // result when the pattern is used. Too low value makes the pattern look |
michael@0 | 7887 | // blurry. Too large value makes it look too crispy. |
michael@0 | 7888 | width = Math.min(Math.ceil(Math.abs(width * combinedScale[0])), |
michael@0 | 7889 | MAX_PATTERN_SIZE); |
michael@0 | 7890 | |
michael@0 | 7891 | height = Math.min(Math.ceil(Math.abs(height * combinedScale[1])), |
michael@0 | 7892 | MAX_PATTERN_SIZE); |
michael@0 | 7893 | |
michael@0 | 7894 | var tmpCanvas = CachedCanvases.getCanvas('pattern', width, height, true); |
michael@0 | 7895 | var tmpCtx = tmpCanvas.context; |
michael@0 | 7896 | var graphics = new CanvasGraphics(tmpCtx, commonObjs, objs); |
michael@0 | 7897 | graphics.groupLevel = owner.groupLevel; |
michael@0 | 7898 | |
michael@0 | 7899 | this.setFillAndStrokeStyleToContext(tmpCtx, paintType, color); |
michael@0 | 7900 | |
michael@0 | 7901 | this.setScale(width, height, xstep, ystep); |
michael@0 | 7902 | this.transformToScale(graphics); |
michael@0 | 7903 | |
michael@0 | 7904 | // transform coordinates to pattern space |
michael@0 | 7905 | var tmpTranslate = [1, 0, 0, 1, -topLeft[0], -topLeft[1]]; |
michael@0 | 7906 | graphics.transform.apply(graphics, tmpTranslate); |
michael@0 | 7907 | |
michael@0 | 7908 | this.clipBbox(graphics, bbox, x0, y0, x1, y1); |
michael@0 | 7909 | |
michael@0 | 7910 | graphics.executeOperatorList(operatorList); |
michael@0 | 7911 | return tmpCanvas.canvas; |
michael@0 | 7912 | }, |
michael@0 | 7913 | |
michael@0 | 7914 | setScale: function TilingPattern_setScale(width, height, xstep, ystep) { |
michael@0 | 7915 | this.scale = [width / xstep, height / ystep]; |
michael@0 | 7916 | }, |
michael@0 | 7917 | |
michael@0 | 7918 | transformToScale: function TilingPattern_transformToScale(graphics) { |
michael@0 | 7919 | var scale = this.scale; |
michael@0 | 7920 | var tmpScale = [scale[0], 0, 0, scale[1], 0, 0]; |
michael@0 | 7921 | graphics.transform.apply(graphics, tmpScale); |
michael@0 | 7922 | }, |
michael@0 | 7923 | |
michael@0 | 7924 | scaleToContext: function TilingPattern_scaleToContext() { |
michael@0 | 7925 | var scale = this.scale; |
michael@0 | 7926 | this.ctx.scale(1 / scale[0], 1 / scale[1]); |
michael@0 | 7927 | }, |
michael@0 | 7928 | |
michael@0 | 7929 | clipBbox: function clipBbox(graphics, bbox, x0, y0, x1, y1) { |
michael@0 | 7930 | if (bbox && isArray(bbox) && 4 == bbox.length) { |
michael@0 | 7931 | var bboxWidth = x1 - x0; |
michael@0 | 7932 | var bboxHeight = y1 - y0; |
michael@0 | 7933 | graphics.rectangle(x0, y0, bboxWidth, bboxHeight); |
michael@0 | 7934 | graphics.clip(); |
michael@0 | 7935 | graphics.endPath(); |
michael@0 | 7936 | } |
michael@0 | 7937 | }, |
michael@0 | 7938 | |
michael@0 | 7939 | setFillAndStrokeStyleToContext: |
michael@0 | 7940 | function setFillAndStrokeStyleToContext(context, paintType, color) { |
michael@0 | 7941 | switch (paintType) { |
michael@0 | 7942 | case PaintType.COLORED: |
michael@0 | 7943 | var ctx = this.ctx; |
michael@0 | 7944 | context.fillStyle = ctx.fillStyle; |
michael@0 | 7945 | context.strokeStyle = ctx.strokeStyle; |
michael@0 | 7946 | break; |
michael@0 | 7947 | case PaintType.UNCOLORED: |
michael@0 | 7948 | var rgbColor = ColorSpace.singletons.rgb.getRgb(color, 0); |
michael@0 | 7949 | var cssColor = Util.makeCssRgb(rgbColor); |
michael@0 | 7950 | context.fillStyle = cssColor; |
michael@0 | 7951 | context.strokeStyle = cssColor; |
michael@0 | 7952 | break; |
michael@0 | 7953 | default: |
michael@0 | 7954 | error('Unsupported paint type: ' + paintType); |
michael@0 | 7955 | } |
michael@0 | 7956 | }, |
michael@0 | 7957 | |
michael@0 | 7958 | getPattern: function TilingPattern_getPattern(ctx, owner) { |
michael@0 | 7959 | var temporaryPatternCanvas = this.createPatternCanvas(owner); |
michael@0 | 7960 | |
michael@0 | 7961 | ctx = this.ctx; |
michael@0 | 7962 | ctx.setTransform.apply(ctx, this.baseTransform); |
michael@0 | 7963 | ctx.transform.apply(ctx, this.matrix); |
michael@0 | 7964 | this.scaleToContext(); |
michael@0 | 7965 | |
michael@0 | 7966 | return ctx.createPattern(temporaryPatternCanvas, 'repeat'); |
michael@0 | 7967 | } |
michael@0 | 7968 | }; |
michael@0 | 7969 | |
michael@0 | 7970 | return TilingPattern; |
michael@0 | 7971 | })(); |
michael@0 | 7972 | |
michael@0 | 7973 | |
michael@0 | 7974 | PDFJS.disableFontFace = false; |
michael@0 | 7975 | |
michael@0 | 7976 | var FontLoader = { |
michael@0 | 7977 | insertRule: function fontLoaderInsertRule(rule) { |
michael@0 | 7978 | var styleElement = document.getElementById('PDFJS_FONT_STYLE_TAG'); |
michael@0 | 7979 | if (!styleElement) { |
michael@0 | 7980 | styleElement = document.createElement('style'); |
michael@0 | 7981 | styleElement.id = 'PDFJS_FONT_STYLE_TAG'; |
michael@0 | 7982 | document.documentElement.getElementsByTagName('head')[0].appendChild( |
michael@0 | 7983 | styleElement); |
michael@0 | 7984 | } |
michael@0 | 7985 | |
michael@0 | 7986 | var styleSheet = styleElement.sheet; |
michael@0 | 7987 | styleSheet.insertRule(rule, styleSheet.cssRules.length); |
michael@0 | 7988 | }, |
michael@0 | 7989 | |
michael@0 | 7990 | clear: function fontLoaderClear() { |
michael@0 | 7991 | var styleElement = document.getElementById('PDFJS_FONT_STYLE_TAG'); |
michael@0 | 7992 | if (styleElement) { |
michael@0 | 7993 | styleElement.parentNode.removeChild(styleElement); |
michael@0 | 7994 | } |
michael@0 | 7995 | }, |
michael@0 | 7996 | bind: function fontLoaderBind(fonts, callback) { |
michael@0 | 7997 | assert(!isWorker, 'bind() shall be called from main thread'); |
michael@0 | 7998 | |
michael@0 | 7999 | for (var i = 0, ii = fonts.length; i < ii; i++) { |
michael@0 | 8000 | var font = fonts[i]; |
michael@0 | 8001 | if (font.attached) { |
michael@0 | 8002 | continue; |
michael@0 | 8003 | } |
michael@0 | 8004 | |
michael@0 | 8005 | font.attached = true; |
michael@0 | 8006 | font.bindDOM() |
michael@0 | 8007 | } |
michael@0 | 8008 | |
michael@0 | 8009 | setTimeout(callback); |
michael@0 | 8010 | } |
michael@0 | 8011 | }; |
michael@0 | 8012 | |
michael@0 | 8013 | var FontFace = (function FontFaceClosure() { |
michael@0 | 8014 | function FontFace(name, file, properties) { |
michael@0 | 8015 | this.compiledGlyphs = {}; |
michael@0 | 8016 | if (arguments.length === 1) { |
michael@0 | 8017 | // importing translated data |
michael@0 | 8018 | var data = arguments[0]; |
michael@0 | 8019 | for (var i in data) { |
michael@0 | 8020 | this[i] = data[i]; |
michael@0 | 8021 | } |
michael@0 | 8022 | return; |
michael@0 | 8023 | } |
michael@0 | 8024 | } |
michael@0 | 8025 | FontFace.prototype = { |
michael@0 | 8026 | bindDOM: function FontFace_bindDOM() { |
michael@0 | 8027 | if (!this.data) { |
michael@0 | 8028 | return null; |
michael@0 | 8029 | } |
michael@0 | 8030 | |
michael@0 | 8031 | if (PDFJS.disableFontFace) { |
michael@0 | 8032 | this.disableFontFace = true; |
michael@0 | 8033 | return null; |
michael@0 | 8034 | } |
michael@0 | 8035 | |
michael@0 | 8036 | var data = bytesToString(new Uint8Array(this.data)); |
michael@0 | 8037 | var fontName = this.loadedName; |
michael@0 | 8038 | |
michael@0 | 8039 | // Add the font-face rule to the document |
michael@0 | 8040 | var url = ('url(data:' + this.mimetype + ';base64,' + |
michael@0 | 8041 | window.btoa(data) + ');'); |
michael@0 | 8042 | var rule = '@font-face { font-family:"' + fontName + '";src:' + url + '}'; |
michael@0 | 8043 | FontLoader.insertRule(rule); |
michael@0 | 8044 | |
michael@0 | 8045 | if (PDFJS.pdfBug && 'FontInspector' in globalScope && |
michael@0 | 8046 | globalScope['FontInspector'].enabled) { |
michael@0 | 8047 | globalScope['FontInspector'].fontAdded(this, url); |
michael@0 | 8048 | } |
michael@0 | 8049 | |
michael@0 | 8050 | return rule; |
michael@0 | 8051 | }, |
michael@0 | 8052 | |
michael@0 | 8053 | getPathGenerator: function (objs, character) { |
michael@0 | 8054 | if (!(character in this.compiledGlyphs)) { |
michael@0 | 8055 | var js = objs.get(this.loadedName + '_path_' + character); |
michael@0 | 8056 | /*jshint -W054 */ |
michael@0 | 8057 | this.compiledGlyphs[character] = new Function('c', 'size', js); |
michael@0 | 8058 | } |
michael@0 | 8059 | return this.compiledGlyphs[character]; |
michael@0 | 8060 | } |
michael@0 | 8061 | }; |
michael@0 | 8062 | return FontFace; |
michael@0 | 8063 | })(); |
michael@0 | 8064 | |
michael@0 | 8065 | |
michael@0 | 8066 | }).call((typeof window === 'undefined') ? this : window); |
michael@0 | 8067 | |
michael@0 | 8068 | |
michael@0 | 8069 |