blob: 4cfb3d90ede90a77d8ef63aa8b6100ecbbb8593d [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('print_preview', function() {
'use strict';
class PreviewGenerator extends cr.EventTarget {
/**
* Interface to the Chromium print preview generator.
* @param {!print_preview.DestinationStore} destinationStore Used to get the
* currently selected destination.
* @param {!print_preview.PrintTicketStore} printTicketStore Used to read
* the state of the ticket and write document information.
* @param {!print_preview.NativeLayer} nativeLayer Used to communicate to
* Chromium's preview rendering system.
* @param {!print_preview.DocumentInfo} documentInfo Document data model.
* @param {!WebUIListenerTracker} listenerTracker Tracker for the WebUI
* listeners added in the PreviewGenerator constructor.
*/
constructor(
destinationStore, printTicketStore, nativeLayer, documentInfo,
listenerTracker) {
super();
/**
* Used to get the currently selected destination.
* @private {!print_preview.DestinationStore}
*/
this.destinationStore_ = destinationStore;
/**
* Used to read the state of the ticket and write document information.
* @private {!print_preview.PrintTicketStore}
*/
this.printTicketStore_ = printTicketStore;
/**
* Interface to the Chromium native layer.
* @private {!print_preview.NativeLayer}
*/
this.nativeLayer_ = nativeLayer;
/**
* Document data model.
* @private {!print_preview.DocumentInfo}
*/
this.documentInfo_ = documentInfo;
/**
* ID of current in-flight request. Requests that do not share this ID
* will be ignored.
* @private {number}
*/
this.inFlightRequestId_ = -1;
/**
* Whether the current in flight request requires generating draft pages
* for print preview. This is true only for modifiable documents when the
* print settings has changed sufficiently to require re-rendering.
* @private {boolean}
*/
this.generateDraft_ = false;
/**
* Media size to generate preview with. {@code null} indicates default
* size.
* @private {print_preview.ValueType}
*/
this.mediaSize_ = null;
/**
* Whether the previews are being generated in landscape mode.
* @private {boolean}
*/
this.isLandscapeEnabled_ = false;
/**
* Whether the previews are being generated with a header and footer.
* @private {boolean}
*/
this.isHeaderFooterEnabled_ = false;
/**
* Whether the previews are being generated in color.
* @private {boolean}
*/
this.colorValue_ = false;
/**
* Whether the document should be fitted to the page.
* @private {boolean}
*/
this.isFitToPageEnabled_ = false;
/**
* The scaling factor (in percent) for the document. Ignored if fit to
* page is true.
* @private {number}
*/
this.scalingValue_ = 100;
/**
* Page ranges setting used used to generate the last preview.
* @private {Array<{from: number, to: number}>}
*/
this.pageRanges_ = null;
/**
* Margins type used to generate the last preview.
* @private {!print_preview.ticket_items.MarginsTypeValue}
*/
this.marginsType_ = print_preview.ticket_items.MarginsTypeValue.DEFAULT;
/**
* Whether the document should have element CSS backgrounds printed.
* @private {boolean}
*/
this.isCssBackgroundEnabled_ = false;
/**
* Whether the document should have only the selected area printed.
* @private {boolean}
*/
this.isSelectionOnlyEnabled_ = false;
/**
* Destination that was selected for the last preview.
* @private {print_preview.Destination}
*/
this.selectedDestination_ = null;
this.addWebUIEventListeners_(listenerTracker);
}
/**
* Starts listening for relevant WebUI events and adds the listeners to
* |listenerTracker|. |listenerTracker| is responsible for removing the
* listeners when necessary.
* @param {!WebUIListenerTracker} listenerTracker
* @private
*/
addWebUIEventListeners_(listenerTracker) {
listenerTracker.add(
'page-count-ready', this.onPageCountReady_.bind(this));
listenerTracker.add(
'page-layout-ready', this.onPageLayoutReady_.bind(this));
listenerTracker.add(
'page-preview-ready', this.onPagePreviewReady_.bind(this));
}
/**
* Request that new preview be generated. A preview request will not be
* generated if the print ticket has not changed sufficiently.
* @return {{id: number,
* request: Promise}} The preview request id, or -1 if no preview
* was requested, and a promise that will resolve when the preview is
* complete (null if no preview was actually requested).
*/
requestPreview() {
if (!this.printTicketStore_.isTicketValidForPreview() ||
!this.printTicketStore_.isInitialized ||
!this.destinationStore_.selectedDestination) {
return {id: -1, request: null};
}
const previewChanged = this.hasPreviewChanged_();
if (!previewChanged && !this.hasPreviewPageRangeChanged_()) {
// Changes to these ticket items might not trigger a new preview, but
// they still need to be recorded.
this.marginsType_ = this.printTicketStore_.marginsType.getValue();
return {id: -1, request: null};
}
this.mediaSize_ = this.printTicketStore_.mediaSize.getValue();
this.isLandscapeEnabled_ = this.printTicketStore_.landscape.getValue();
this.isHeaderFooterEnabled_ =
this.printTicketStore_.headerFooter.getValue();
this.colorValue_ = this.printTicketStore_.color.getValue();
this.isFitToPageEnabled_ = this.printTicketStore_.fitToPage.getValue();
this.scalingValue_ = this.printTicketStore_.scaling.getValueAsNumber();
this.pageRanges_ = this.printTicketStore_.pageRange.getPageRanges();
this.marginsType_ = this.printTicketStore_.marginsType.getValue();
this.isCssBackgroundEnabled_ =
this.printTicketStore_.cssBackground.getValue();
this.isSelectionOnlyEnabled_ =
this.printTicketStore_.selectionOnly.getValue();
this.selectedDestination_ = this.destinationStore_.selectedDestination;
this.inFlightRequestId_++;
this.generateDraft_ = this.documentInfo_.isModifiable;
return {
id: this.inFlightRequestId_,
request: this.getPreview_(),
};
}
/**
* @return {!Promise} Promise that resolves when the preview has been
* generated.
* @private
*/
getPreview_() {
const printTicketStore = this.printTicketStore_;
const destination = assert(this.destinationStore_.selectedDestination);
assert(
printTicketStore.isTicketValidForPreview(),
'Trying to generate preview when ticket is not valid');
const ticket = {
pageRange: printTicketStore.pageRange.getDocumentPageRanges(),
mediaSize: printTicketStore.mediaSize.getValue(),
landscape: printTicketStore.landscape.getValue(),
color:
destination.getNativeColorModel(printTicketStore.color.getValue()),
headerFooterEnabled: printTicketStore.headerFooter.getValue(),
marginsType: printTicketStore.marginsType.getValue(),
isFirstRequest: this.inFlightRequestId_ == 0,
requestID: this.inFlightRequestId_,
previewModifiable: this.documentInfo_.isModifiable,
generateDraftData: this.generateDraft_,
fitToPageEnabled: printTicketStore.fitToPage.getValue(),
scaleFactor: printTicketStore.scaling.getValueAsNumber(),
// NOTE: Even though the following fields dont directly relate to the
// preview, they still need to be included.
// e.g. printing::PrintSettingsFromJobSettings() still checks for them.
collate: true,
copies: 1,
deviceName: destination.id,
dpiHorizontal: 'horizontal_dpi' in printTicketStore.dpi.getValue() ?
printTicketStore.dpi.getValue().horizontal_dpi :
0,
dpiVertical: 'vertical_dpi' in printTicketStore.dpi.getValue() ?
printTicketStore.dpi.getValue().vertical_dpi :
0,
duplex: printTicketStore.duplex.getValue() ?
PreviewGenerator.DuplexMode.LONG_EDGE :
PreviewGenerator.DuplexMode.SIMPLEX,
printToPDF: destination.id ==
print_preview.Destination.GooglePromotedId.SAVE_AS_PDF,
printWithCloudPrint: !destination.isLocal,
printWithPrivet: destination.isPrivet,
printWithExtension: destination.isExtension,
rasterizePDF: false,
shouldPrintBackgrounds: printTicketStore.cssBackground.getValue(),
shouldPrintSelectionOnly: printTicketStore.selectionOnly.getValue()
};
// Set 'cloudPrintID' only if the destination is not local.
if (destination && !destination.isLocal) {
ticket.cloudPrintID = destination.id;
}
if (printTicketStore.marginsType.isCapabilityAvailable() &&
printTicketStore.marginsType.getValue() ==
print_preview.ticket_items.MarginsTypeValue.CUSTOM) {
const customMargins = printTicketStore.customMargins.getValue();
const orientationEnum =
print_preview.ticket_items.CustomMarginsOrientation;
ticket.marginsCustom = {
marginTop: customMargins.get(orientationEnum.TOP),
marginRight: customMargins.get(orientationEnum.RIGHT),
marginBottom: customMargins.get(orientationEnum.BOTTOM),
marginLeft: customMargins.get(orientationEnum.LEFT)
};
}
const pageCount =
this.inFlightRequestId_ > 0 ? this.documentInfo_.pageCount : -1;
return this.nativeLayer_.getPreview(JSON.stringify(ticket), pageCount);
}
/**
* Dispatches a PAGE_READY event to signal that a page preview is ready.
* @param {number} previewIndex Index of the page with respect to the pages
* shown in the preview. E.g an index of 0 is the first displayed page,
* but not necessarily the first original document page.
* @param {number} pageNumber Number of the page with respect to the
* document. A value of 3 means it's the third page of the original
* document.
* @param {number} previewUid Unique identifier of the preview.
* @private
*/
dispatchPageReadyEvent_(previewIndex, pageNumber, previewUid) {
const pageGenEvent = new Event(PreviewGenerator.EventType.PAGE_READY);
pageGenEvent.previewIndex = previewIndex;
pageGenEvent.previewUrl = 'chrome://print/' + previewUid.toString() +
'/' + (pageNumber - 1) + '/print.pdf';
this.dispatchEvent(pageGenEvent);
}
/**
* Dispatches a PREVIEW_START event. Signals that the preview should be
* reloaded.
* @param {number} previewUid Unique identifier of the preview.
* @param {number} index Index of the first page of the preview.
* @private
*/
dispatchPreviewStartEvent_(previewUid, index) {
const previewStartEvent =
new Event(PreviewGenerator.EventType.PREVIEW_START);
if (!this.documentInfo_.isModifiable) {
index = -1;
}
previewStartEvent.previewUrl = 'chrome://print/' + previewUid.toString() +
'/' + index + '/print.pdf';
this.dispatchEvent(previewStartEvent);
}
/**
* @return {boolean} Whether the print ticket, excluding the page range, has
* changed sufficiently to determine whether a new preview request
* should be issued.
* @private
*/
hasPreviewChanged_() {
const ticketStore = this.printTicketStore_;
return this.inFlightRequestId_ == -1 ||
!ticketStore.mediaSize.isValueEqual(this.mediaSize_) ||
!ticketStore.landscape.isValueEqual(this.isLandscapeEnabled_) ||
!ticketStore.headerFooter.isValueEqual(this.isHeaderFooterEnabled_) ||
!ticketStore.color.isValueEqual(this.colorValue_) ||
!ticketStore.scaling.isValueEqual(this.scalingValue_) ||
!ticketStore.fitToPage.isValueEqual(this.isFitToPageEnabled_) ||
(!ticketStore.marginsType.isValueEqual(this.marginsType_) &&
!ticketStore.marginsType.isValueEqual(
print_preview.ticket_items.MarginsTypeValue.CUSTOM)) ||
(ticketStore.marginsType.isValueEqual(
print_preview.ticket_items.MarginsTypeValue.CUSTOM) &&
!ticketStore.customMargins.isValueEqual(
this.documentInfo_.margins)) ||
!ticketStore.cssBackground.isValueEqual(
this.isCssBackgroundEnabled_) ||
!ticketStore.selectionOnly.isValueEqual(
this.isSelectionOnlyEnabled_) ||
(this.selectedDestination_ !=
this.destinationStore_.selectedDestination);
}
/**
* @return {boolean} Whether the page range in the print ticket has changed.
* @private
*/
hasPreviewPageRangeChanged_() {
return this.pageRanges_ == null ||
!areRangesEqual(
this.printTicketStore_.pageRange.getPageRanges(),
this.pageRanges_);
}
/**
* Called when the page layout of the document is ready. Always occurs
* as a result of a preview request.
* @param {{marginTop: number,
* marginLeft: number,
* marginBottom: number,
* marginRight: number,
* contentWidth: number,
* contentHeight: number,
* printableAreaX: number,
* printableAreaY: number,
* printableAreaWidth: number,
* printableAreaHeight: number,
* }} pageLayout Layout information about the document.
* @param {boolean} hasCustomPageSizeStyle Whether this document has a
* custom page size or style to use.
* @private
*/
onPageLayoutReady_(pageLayout, hasCustomPageSizeStyle) {
// NOTE: A request ID is not specified, so assuming its for the current
// in-flight request.
const origin = new print_preview.Coordinate2d(
pageLayout.printableAreaX, pageLayout.printableAreaY);
const size = new print_preview.Size(
pageLayout.printableAreaWidth, pageLayout.printableAreaHeight);
const margins = new print_preview.Margins(
Math.round(pageLayout.marginTop), Math.round(pageLayout.marginRight),
Math.round(pageLayout.marginBottom),
Math.round(pageLayout.marginLeft));
const o = print_preview.ticket_items.CustomMarginsOrientation;
const pageSize = new print_preview.Size(
pageLayout.contentWidth + margins.get(o.LEFT) + margins.get(o.RIGHT),
pageLayout.contentHeight + margins.get(o.TOP) +
margins.get(o.BOTTOM));
this.documentInfo_.updatePageInfo(
new print_preview.PrintableArea(origin, size), pageSize,
hasCustomPageSizeStyle, margins);
}
/**
* Called when the document page count is received from the native layer.
* Always occurs as a result of a preview request.
* @param {number} pageCount The document's page count.
* @param {number} previewResponseId The request ID that corresponds to this
* page count.
* @param {number} fitToPageScaling The scaling required to fit the document
* to page (unused).
* @private
*/
onPageCountReady_(pageCount, previewResponseId, fitToPageScaling) {
if (this.inFlightRequestId_ != previewResponseId) {
return; // Ignore old response.
}
this.documentInfo_.updatePageCount(pageCount);
this.pageRanges_ = this.printTicketStore_.pageRange.getPageRanges();
}
/**
* Called when a page's preview has been generated. Dispatches a
* PAGE_READY event.
* @param {number} pageIndex The index of the page whose preview is ready.
* @param {number} previewUid The unique ID of the print preview UI.
* @param {number} previewResponseId The preview request ID that this page
* preview is a response to.
* @private
*/
onPagePreviewReady_(pageIndex, previewUid, previewResponseId) {
if (this.inFlightRequestId_ != previewResponseId) {
return; // Ignore old response.
}
const pageNumber = pageIndex + 1;
const pageNumberSet = this.printTicketStore_.pageRange.getPageNumberSet();
if (pageNumberSet.hasPageNumber(pageNumber)) {
const previewIndex = pageNumberSet.getPageNumberIndex(pageNumber);
if (previewIndex == 0) {
this.dispatchPreviewStartEvent_(previewUid, pageIndex);
}
this.dispatchPageReadyEvent_(previewIndex, pageNumber, previewUid);
}
}
/**
* Called when the preview generation is complete. Dispatches a
* DOCUMENT_READY event.
* @param {number} previewResponseId
* @param {number} previewUid
*/
onPreviewGenerationDone(previewResponseId, previewUid) {
if (this.inFlightRequestId_ != previewResponseId) {
return; // Ignore old response.
}
if (!this.generateDraft_) {
// Dispatch a PREVIEW_START event since not generating a draft PDF,
// which includes print preview for non-modifiable documents, does not
// trigger PAGE_READY events.
this.dispatchPreviewStartEvent_(previewUid, 0);
}
cr.dispatchSimpleEvent(this, PreviewGenerator.EventType.DOCUMENT_READY);
}
/**
* Called when the preview generation fails.
* @private
*/
onPreviewGenerationFail_() {
// NOTE: No request ID is returned from Chromium so its assumed its the
// current one.
cr.dispatchSimpleEvent(this, PreviewGenerator.EventType.FAIL);
}
}
/**
* Event types dispatched by the preview generator.
* @enum {string}
*/
PreviewGenerator.EventType = {
// Dispatched when the document can be printed.
DOCUMENT_READY: 'print_preview.PreviewGenerator.DOCUMENT_READY',
// Dispatched when a page preview is ready. The previewIndex field of the
// event is the index of the page in the modified document, not the
// original. So page 4 of the original document might be previewIndex = 0 of
// the modified document.
PAGE_READY: 'print_preview.PreviewGenerator.PAGE_READY',
// Dispatched when the document preview starts to be generated.
PREVIEW_START: 'print_preview.PreviewGenerator.PREVIEW_START',
// Dispatched when the current print preview request fails.
FAIL: 'print_preview.PreviewGenerator.FAIL'
};
/**
* Constant values matching printing::DuplexMode enum.
* @enum {number}
*/
PreviewGenerator
.DuplexMode = {SIMPLEX: 0, LONG_EDGE: 1, UNKNOWN_DUPLEX_MODE: -1};
// Export
return {PreviewGenerator: PreviewGenerator};
});