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