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