preview_generator.js revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5cr.define('print_preview', function() { 6 'use strict'; 7 8 /** 9 * Interface to the Chromium print preview generator. 10 * @param {!print_preview.DestinationStore} destinationStore Used to get the 11 * currently selected destination. 12 * @param {!print_preview.PrintTicketStore} printTicketStore Used to read the 13 * state of the ticket and write document information. 14 * @param {!print_preview.NativeLayer} nativeLayer Used to communicate to 15 * Chromium's preview rendering system. 16 * @param {!print_preview.DocumentInfo} documentInfo Document data model. 17 * @constructor 18 * @extends {cr.EventTarget} 19 */ 20 function PreviewGenerator( 21 destinationStore, printTicketStore, nativeLayer, documentInfo) { 22 cr.EventTarget.call(this); 23 24 /** 25 * Used to get the currently selected destination. 26 * @type {!print_preview.DestinationStore} 27 * @private 28 */ 29 this.destinationStore_ = destinationStore; 30 31 /** 32 * Used to read the state of the ticket and write document information. 33 * @type {!print_preview.PrintTicketStore} 34 * @private 35 */ 36 this.printTicketStore_ = printTicketStore; 37 38 /** 39 * Interface to the Chromium native layer. 40 * @type {!print_preview.NativeLayer} 41 * @private 42 */ 43 this.nativeLayer_ = nativeLayer; 44 45 /** 46 * Document data model. 47 * @type {!print_preview.DocumentInfo} 48 * @private 49 */ 50 this.documentInfo_ = documentInfo; 51 52 /** 53 * ID of current in-flight request. Requests that do not share this ID will 54 * be ignored. 55 * @type {number} 56 * @private 57 */ 58 this.inFlightRequestId_ = -1; 59 60 /** 61 * Whether the previews are being generated in landscape mode. 62 * @type {boolean} 63 * @private 64 */ 65 this.isLandscapeEnabled_ = false; 66 67 /** 68 * Whether the previews are being generated with a header and footer. 69 * @type {boolean} 70 * @private 71 */ 72 this.isHeaderFooterEnabled_ = false; 73 74 /** 75 * Whether the previews are being generated in color. 76 * @type {boolean} 77 * @private 78 */ 79 this.colorValue_ = false; 80 81 /** 82 * Whether the document should be fitted to the page. 83 * @type {boolean} 84 * @private 85 */ 86 this.isFitToPageEnabled_ = false; 87 88 /** 89 * Page ranges setting used used to generate the last preview. 90 * @type {!Array.<object.<{from: number, to: number}>>} 91 * @private 92 */ 93 this.pageRanges_ = null; 94 95 /** 96 * Margins type used to generate the last preview. 97 * @type {!print_preview.ticket_items.MarginsType.Value} 98 * @private 99 */ 100 this.marginsType_ = print_preview.ticket_items.MarginsType.Value.DEFAULT; 101 102 /** 103 * Whether the document should have element CSS backgrounds printed. 104 * @type {boolean} 105 * @private 106 */ 107 this.isCssBackgroundEnabled_ = false; 108 109 /** 110 * Destination that was selected for the last preview. 111 * @type {print_preview.Destination} 112 * @private 113 */ 114 this.selectedDestination_ = null; 115 116 /** 117 * Event tracker used to keep track of native layer events. 118 * @type {!EventTracker} 119 * @private 120 */ 121 this.tracker_ = new EventTracker(); 122 123 this.addEventListeners_(); 124 }; 125 126 /** 127 * Event types dispatched by the preview generator. 128 * @enum {string} 129 */ 130 PreviewGenerator.EventType = { 131 // Dispatched when the document can be printed. 132 DOCUMENT_READY: 'print_preview.PreviewGenerator.DOCUMENT_READY', 133 134 // Dispatched when a page preview is ready. The previewIndex field of the 135 // event is the index of the page in the modified document, not the 136 // original. So page 4 of the original document might be previewIndex = 0 of 137 // the modified document. 138 PAGE_READY: 'print_preview.PreviewGenerator.PAGE_READY', 139 140 // Dispatched when the document preview starts to be generated. 141 PREVIEW_START: 'print_preview.PreviewGenerator.PREVIEW_START', 142 143 // Dispatched when the current print preview request fails. 144 FAIL: 'print_preview.PreviewGenerator.FAIL' 145 }; 146 147 PreviewGenerator.prototype = { 148 __proto__: cr.EventTarget.prototype, 149 150 /** 151 * Request that new preview be generated. A preview request will not be 152 * generated if the print ticket has not changed sufficiently. 153 * @return {boolean} Whether a new preview was actually requested. 154 */ 155 requestPreview: function() { 156 if (!this.printTicketStore_.isTicketValidForPreview() || 157 !this.destinationStore_.selectedDestination) { 158 return false; 159 } 160 if (!this.hasPreviewChanged_()) { 161 // Changes to these ticket items might not trigger a new preview, but 162 // they still need to be recorded. 163 this.marginsType_ = this.printTicketStore_.getMarginsType(); 164 return false; 165 } 166 this.isLandscapeEnabled_ = this.printTicketStore_.isLandscapeEnabled(); 167 this.isHeaderFooterEnabled_ = 168 this.printTicketStore_.isHeaderFooterEnabled(); 169 this.colorValue_ = this.printTicketStore_.color.getValue(); 170 this.isFitToPageEnabled_ = this.printTicketStore_.fitToPage.getValue(); 171 this.pageRanges_ = this.printTicketStore_.getPageRanges(); 172 this.marginsType_ = this.printTicketStore_.getMarginsType(); 173 this.isCssBackgroundEnabled_ = 174 this.printTicketStore_.cssBackground.getValue(); 175 this.isSelectionOnlyEnabled_ = 176 this.printTicketStore_.selectionOnly.getValue(); 177 this.selectedDestination_ = this.destinationStore_.selectedDestination; 178 179 this.inFlightRequestId_++; 180 this.nativeLayer_.startGetPreview( 181 this.destinationStore_.selectedDestination, 182 this.printTicketStore_, 183 this.documentInfo_, 184 this.inFlightRequestId_); 185 return true; 186 }, 187 188 /** Removes all event listeners that the preview generator has attached. */ 189 removeEventListeners: function() { 190 this.tracker_.removeAll(); 191 }, 192 193 /** 194 * Adds event listeners to the relevant native layer events. 195 * @private 196 */ 197 addEventListeners_: function() { 198 this.tracker_.add( 199 this.nativeLayer_, 200 print_preview.NativeLayer.EventType.PAGE_LAYOUT_READY, 201 this.onPageLayoutReady_.bind(this)); 202 this.tracker_.add( 203 this.nativeLayer_, 204 print_preview.NativeLayer.EventType.PAGE_COUNT_READY, 205 this.onPageCountReady_.bind(this)); 206 this.tracker_.add( 207 this.nativeLayer_, 208 print_preview.NativeLayer.EventType.PREVIEW_RELOAD, 209 this.onPreviewReload_.bind(this)); 210 this.tracker_.add( 211 this.nativeLayer_, 212 print_preview.NativeLayer.EventType.PAGE_PREVIEW_READY, 213 this.onPagePreviewReady_.bind(this)); 214 this.tracker_.add( 215 this.nativeLayer_, 216 print_preview.NativeLayer.EventType.PREVIEW_GENERATION_DONE, 217 this.onPreviewGenerationDone_.bind(this)); 218 this.tracker_.add( 219 this.nativeLayer_, 220 print_preview.NativeLayer.EventType.PREVIEW_GENERATION_FAIL, 221 this.onPreviewGenerationFail_.bind(this)); 222 }, 223 224 /** 225 * Dispatches a PAGE_READY event to signal that a page preview is ready. 226 * @param {number} previewIndex Index of the page with respect to the pages 227 * shown in the preview. E.g an index of 0 is the first displayed page, 228 * but not necessarily the first original document page. 229 * @param {number} pageNumber Number of the page with respect to the 230 * document. A value of 3 means it's the third page of the original 231 * document. 232 * @param {number} previewUid Unique identifier of the preview. 233 * @private 234 */ 235 dispatchPageReadyEvent_: function(previewIndex, pageNumber, previewUid) { 236 var pageGenEvent = new cr.Event(PreviewGenerator.EventType.PAGE_READY); 237 pageGenEvent.previewIndex = previewIndex; 238 pageGenEvent.previewUrl = 'chrome://print/' + previewUid.toString() + 239 '/' + (pageNumber - 1) + '/print.pdf'; 240 this.dispatchEvent(pageGenEvent); 241 }, 242 243 /** 244 * Dispatches a PREVIEW_START event. Signals that the preview should be 245 * reloaded. 246 * @param {number} previewUid Unique identifier of the preview. 247 * @param {number} index Index of the first page of the preview. 248 * @private 249 */ 250 dispatchPreviewStartEvent_: function(previewUid, index) { 251 var previewStartEvent = new cr.Event( 252 PreviewGenerator.EventType.PREVIEW_START); 253 if (!this.documentInfo_.isModifiable) { 254 index = -1; 255 } 256 previewStartEvent.previewUrl = 'chrome://print/' + 257 previewUid.toString() + '/' + index + '/print.pdf'; 258 this.dispatchEvent(previewStartEvent); 259 }, 260 261 /** 262 * @return {boolean} Whether the print ticket has changed sufficiently to 263 * determine whether a new preview request should be issued. 264 * @private 265 */ 266 hasPreviewChanged_: function() { 267 var ticketStore = this.printTicketStore_; 268 return this.inFlightRequestId_ == -1 || 269 ticketStore.isLandscapeEnabled() != this.isLandscapeEnabled_ || 270 ticketStore.isHeaderFooterEnabled() != this.isHeaderFooterEnabled_ || 271 !ticketStore.color.isValueEqual(this.colorValue_) || 272 !ticketStore.fitToPage.isValueEqual(this.isFitToPageEnabled_) || 273 this.pageRanges_ == null || 274 !areRangesEqual(ticketStore.getPageRanges(), this.pageRanges_) || 275 (ticketStore.getMarginsType() != this.marginsType_ && 276 ticketStore.getMarginsType() != 277 print_preview.ticket_items.MarginsType.Value.CUSTOM) || 278 (ticketStore.getMarginsType() == 279 print_preview.ticket_items.MarginsType.Value.CUSTOM && 280 !ticketStore.getCustomMargins().equals( 281 this.documentInfo_.margins)) || 282 !ticketStore.cssBackground.isValueEqual( 283 this.isCssBackgroundEnabled_) || 284 !ticketStore.selectionOnly.isValueEqual( 285 this.isSelectionOnlyEnabled_) || 286 (this.selectedDestination_ != 287 this.destinationStore_.selectedDestination); 288 }, 289 290 /** 291 * Called when the page layout of the document is ready. Always occurs 292 * as a result of a preview request. 293 * @param {cr.Event} event Contains layout info about the document. 294 * @private 295 */ 296 onPageLayoutReady_: function(event) { 297 // NOTE: A request ID is not specified, so assuming its for the current 298 // in-flight request. 299 300 var origin = new print_preview.Coordinate2d( 301 event.pageLayout.printableAreaX, 302 event.pageLayout.printableAreaY); 303 var size = new print_preview.Size( 304 event.pageLayout.printableAreaWidth, 305 event.pageLayout.printableAreaHeight); 306 307 var margins = new print_preview.Margins( 308 Math.round(event.pageLayout.marginTop), 309 Math.round(event.pageLayout.marginRight), 310 Math.round(event.pageLayout.marginBottom), 311 Math.round(event.pageLayout.marginLeft)); 312 313 var o = print_preview.ticket_items.CustomMargins.Orientation; 314 var pageSize = new print_preview.Size( 315 event.pageLayout.contentWidth + 316 margins.get(o.LEFT) + margins.get(o.RIGHT), 317 event.pageLayout.contentHeight + 318 margins.get(o.TOP) + margins.get(o.BOTTOM)); 319 320 this.documentInfo_.updatePageInfo( 321 new print_preview.PrintableArea(origin, size), 322 pageSize, 323 event.hasCustomPageSizeStyle, 324 margins); 325 }, 326 327 /** 328 * Called when the document page count is received from the native layer. 329 * Always occurs as a result of a preview request. 330 * @param {cr.Event} event Contains the document's page count. 331 * @private 332 */ 333 onPageCountReady_: function(event) { 334 if (this.inFlightRequestId_ != event.previewResponseId) { 335 return; // Ignore old response. 336 } 337 this.documentInfo_.updatePageCount(event.pageCount); 338 this.pageRanges_ = this.printTicketStore_.getPageRanges(); 339 }, 340 341 /** 342 * Called when the print preview should be reloaded. 343 * @param {cr.Event} event Contains the preview UID and request ID. 344 * @private 345 */ 346 onPreviewReload_: function(event) { 347 if (this.inFlightRequestId_ != event.previewResponseId) { 348 return; // Ignore old response. 349 } 350 var pageNumberSet = this.printTicketStore_.getPageNumberSet(); 351 this.dispatchPreviewStartEvent_( 352 event.previewUid, pageNumberSet.getPageNumberAt(0) - 1); 353 for (var i = 0; i < pageNumberSet.size; i++) { 354 var pageNumber = pageNumberSet.getPageNumberAt(i); 355 this.dispatchPageReadyEvent_(i, pageNumber, event.previewUid); 356 } 357 cr.dispatchSimpleEvent(this, PreviewGenerator.EventType.DOCUMENT_READY); 358 }, 359 360 /** 361 * Called when a page's preview has been generated. Dispatches a 362 * PAGE_READY event. 363 * @param {cr.Event} event Contains the page index and preview UID. 364 * @private 365 */ 366 onPagePreviewReady_: function(event) { 367 if (this.inFlightRequestId_ != event.previewResponseId) { 368 return; // Ignore old response. 369 } 370 var pageNumber = event.pageIndex + 1; 371 if (this.printTicketStore_.getPageNumberSet().hasPageNumber(pageNumber)) { 372 var previewIndex = this.printTicketStore_.getPageNumberSet() 373 .getPageNumberIndex(pageNumber); 374 if (previewIndex == 0) { 375 this.dispatchPreviewStartEvent_(event.previewUid, event.pageIndex); 376 } 377 this.dispatchPageReadyEvent_( 378 previewIndex, pageNumber, event.previewUid); 379 } 380 }, 381 382 /** 383 * Called when the preview generation is complete. Dispatches a 384 * DOCUMENT_READY event. 385 * @param {cr.Event} event Contains the preview UID and response ID. 386 * @private 387 */ 388 onPreviewGenerationDone_: function(event) { 389 if (this.inFlightRequestId_ != event.previewResponseId) { 390 return; // Ignore old response. 391 } 392 // Dispatch a PREVIEW_START event since non-modifiable documents don't 393 // trigger PAGE_READY events. 394 if (!this.documentInfo_.isModifiable) { 395 this.dispatchPreviewStartEvent_(event.previewUid, 0); 396 } 397 cr.dispatchSimpleEvent(this, PreviewGenerator.EventType.DOCUMENT_READY); 398 }, 399 400 /** 401 * Called when the preview generation fails. 402 * @private 403 */ 404 onPreviewGenerationFail_: function() { 405 // NOTE: No request ID is returned from Chromium so its assumed its the 406 // current one. 407 cr.dispatchSimpleEvent(this, PreviewGenerator.EventType.FAIL); 408 } 409 }; 410 411 // Export 412 return { 413 PreviewGenerator: PreviewGenerator 414 }; 415}); 416