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