| // Copyright 2017 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. |
| |
| import {assert} from 'chrome://resources/js/assert_ts.js'; |
| // <if expr="is_chromeos"> |
| import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js'; |
| // </if> |
| import {PromiseResolver} from 'chrome://resources/js/promise_resolver.m.js'; |
| import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; |
| |
| import {BackgroundGraphicsModeRestriction, Policies} from '../native_layer.js'; |
| // <if expr="is_chromeos"> |
| import {ColorModeRestriction, DuplexModeRestriction, PinModeRestriction} from '../native_layer.js'; |
| // </if> |
| import {CapabilityWithReset, Cdd, CddCapabilities, ColorOption, DpiOption, DuplexOption, MediaSizeOption} from './cdd.js'; |
| import {Destination, DestinationOrigin, GooglePromotedDestinationId, PrinterType, RecentDestination} from './destination.js'; |
| import {DocumentSettings} from './document_info.js'; |
| import {CustomMarginsOrientation, Margins, MarginsSetting, MarginsType} from './margins.js'; |
| import {ScalingType} from './scaling.js'; |
| import {Size} from './size.js'; |
| |
| /** |
| * |key| is the field in the serialized settings state that corresponds to the |
| * setting, or an empty string if the setting should not be saved in the |
| * serialized state. |
| */ |
| export interface Setting { |
| value: any; |
| unavailableValue: any; |
| valid: boolean; |
| available: boolean; |
| setByPolicy: boolean; |
| setFromUi: boolean; |
| key: string; |
| updatesPreview: boolean; |
| } |
| |
| export interface Settings { |
| pages: Setting; |
| copies: Setting; |
| collate: Setting; |
| layout: Setting; |
| color: Setting; |
| customMargins: Setting; |
| mediaSize: Setting; |
| margins: Setting; |
| dpi: Setting; |
| scaling: Setting; |
| scalingType: Setting; |
| scalingTypePdf: Setting; |
| duplex: Setting; |
| duplexShortEdge: Setting; |
| cssBackground: Setting; |
| selectionOnly: Setting; |
| headerFooter: Setting; |
| rasterize: Setting; |
| vendorItems: Setting; |
| otherOptions: Setting; |
| ranges: Setting; |
| pagesPerSheet: Setting; |
| // <if expr="is_chromeos"> |
| pin: Setting; |
| pinValue: Setting; |
| // </if> |
| } |
| |
| export interface SerializedSettings { |
| version: number; |
| recentDestinations?: RecentDestination[]; |
| dpi?: DpiOption; |
| mediaSize?: MediaSizeOption; |
| marginsType?: MarginsType; |
| customMargins?: MarginsSetting; |
| isColorEnabled?: boolean; |
| isDuplexEnabled?: boolean; |
| isDuplexShortEdge?: boolean; |
| isHeaderFooterEnabled?: boolean; |
| isLandscapeEnabled?: boolean; |
| isCollateEnabled?: boolean; |
| isCssBackgroundEnabled?: boolean; |
| scaling?: string; |
| scalingType?: ScalingType; |
| scalingTypePdf?: ScalingType; |
| vendorOptions?: object; |
| // <if expr="is_chromeos"> |
| isPinEnabled?: boolean; |
| pinValue?: string; |
| // </if> |
| } |
| |
| export interface PolicyEntry { |
| value: any; |
| managed: boolean; |
| applyOnDestinationUpdate: boolean; |
| } |
| |
| export interface PolicyObjectEntry { |
| defaultMode?: any; |
| allowedMode?: any; |
| value?: number; |
| } |
| |
| export interface PolicySettings { |
| headerFooter?: PolicyEntry; |
| cssBackground?: PolicyEntry; |
| mediaSize?: PolicyEntry; |
| sheets?: PolicyEntry; |
| color?: PolicyEntry; |
| duplex?: PolicyEntry; |
| pin?: PolicyEntry; |
| printPdfAsImageAvailability?: PolicyEntry; |
| printPdfAsImage?: PolicyEntry; |
| } |
| |
| interface CloudJobTicketPrint { |
| page_orientation?: object; |
| dpi?: object; |
| vendor_ticket_item?: object[]; |
| copies?: object; |
| media_size?: object; |
| duplex?: object; |
| color?: {vendor_id?: string, type?: string}; |
| collate?: object; |
| } |
| |
| interface CloudJobTicket { |
| version: string; |
| print: CloudJobTicketPrint; |
| } |
| |
| export interface MediaSizeValue { |
| width_microns: number; |
| height_microns: number; |
| } |
| |
| export interface Ticket { |
| collate: boolean; |
| color: number; |
| copies: number; |
| deviceName: string; |
| dpiHorizontal: number; |
| dpiVertical: number; |
| duplex: DuplexMode; |
| headerFooterEnabled: boolean; |
| landscape: boolean; |
| marginsType: MarginsType; |
| mediaSize: MediaSizeValue; |
| pagesPerSheet: number; |
| previewModifiable: boolean; |
| printerType: PrinterType; |
| rasterizePDF: boolean; |
| scaleFactor: number; |
| scalingType: ScalingType; |
| shouldPrintBackgrounds: boolean; |
| shouldPrintSelectionOnly: boolean; |
| advancedSettings?: object; |
| capabilities?: string; |
| marginsCustom?: MarginsSetting; |
| openPDFInPreview?: boolean; |
| pinValue?: string; |
| ticket?: string; |
| } |
| |
| export type PrintTicket = Ticket&{ |
| dpiDefault: boolean, |
| pageCount: number, |
| pageHeight: number, |
| pageWidth: number, |
| // <if expr="is_chromeos"> |
| printToGoogleDrive: boolean, |
| // </if> |
| showSystemDialog: boolean, |
| }; |
| |
| /** |
| * Constant values matching printing::DuplexMode enum. |
| */ |
| export enum DuplexMode { |
| SIMPLEX = 0, |
| LONG_EDGE = 1, |
| SHORT_EDGE = 2, |
| UNKNOWN_DUPLEX_MODE = -1, |
| } |
| |
| /** |
| * Values matching the types of duplex in a CDD. |
| */ |
| export enum DuplexType { |
| NO_DUPLEX = 'NO_DUPLEX', |
| LONG_EDGE = 'LONG_EDGE', |
| SHORT_EDGE = 'SHORT_EDGE', |
| } |
| |
| let instance: PrintPreviewModelElement|null = null; |
| |
| let whenReadyResolver: PromiseResolver<void> = new PromiseResolver(); |
| |
| export function getInstance(): PrintPreviewModelElement { |
| assert(instance); |
| return instance; |
| } |
| |
| export function whenReady(): Promise<void> { |
| return whenReadyResolver.promise; |
| } |
| |
| /** |
| * Sticky setting names in alphabetical order. |
| */ |
| const STICKY_SETTING_NAMES: string[] = [ |
| 'recentDestinations', |
| 'collate', |
| 'color', |
| 'cssBackground', |
| 'customMargins', |
| 'dpi', |
| 'duplex', |
| 'duplexShortEdge', |
| 'headerFooter', |
| 'layout', |
| 'margins', |
| 'mediaSize', |
| 'scaling', |
| 'scalingType', |
| 'scalingTypePdf', |
| 'vendorItems', |
| ]; |
| // <if expr="is_chromeos"> |
| STICKY_SETTING_NAMES.push('pin', 'pinValue'); |
| // </if> |
| |
| /** |
| * Minimum height of page in microns to allow headers and footers. Should |
| * match the value for min_size_printer_units in printing/print_settings.cc |
| * so that we do not request header/footer for margins that will be zero. |
| */ |
| const MINIMUM_HEIGHT_MICRONS: number = 25400; |
| |
| export class PrintPreviewModelElement extends PolymerElement { |
| static get is() { |
| return 'print-preview-model'; |
| } |
| |
| static get template() { |
| return null; |
| } |
| |
| static get properties() { |
| return { |
| /** |
| * Object containing current settings of Print Preview, for use by Polymer |
| * controls. |
| * Initialize all settings to available so that more settings always stays |
| * in a collapsed state during startup, when document information and |
| * printer capabilities may arrive at slightly different times. |
| */ |
| settings: { |
| type: Object, |
| notify: true, |
| value() { |
| return { |
| pages: { |
| value: [1], |
| unavailableValue: [], |
| valid: true, |
| available: true, |
| setByPolicy: false, |
| setFromUi: false, |
| key: '', |
| updatesPreview: false, |
| }, |
| copies: { |
| value: 1, |
| unavailableValue: 1, |
| valid: true, |
| available: true, |
| setByPolicy: false, |
| setFromUi: false, |
| key: '', |
| updatesPreview: false, |
| }, |
| collate: { |
| value: true, |
| unavailableValue: false, |
| valid: true, |
| available: true, |
| setByPolicy: false, |
| setFromUi: false, |
| key: 'isCollateEnabled', |
| updatesPreview: false, |
| }, |
| layout: { |
| value: false, /* portrait */ |
| unavailableValue: false, |
| valid: true, |
| available: true, |
| setByPolicy: false, |
| setFromUi: false, |
| key: 'isLandscapeEnabled', |
| updatesPreview: true, |
| }, |
| color: { |
| value: true, /* color */ |
| unavailableValue: false, |
| valid: true, |
| available: true, |
| setByPolicy: false, |
| setFromUi: false, |
| key: 'isColorEnabled', |
| updatesPreview: true, |
| }, |
| mediaSize: { |
| value: {}, |
| unavailableValue: { |
| width_microns: 215900, |
| height_microns: 279400, |
| }, |
| valid: true, |
| available: true, |
| setByPolicy: false, |
| setFromUi: false, |
| key: 'mediaSize', |
| updatesPreview: true, |
| }, |
| margins: { |
| value: MarginsType.DEFAULT, |
| unavailableValue: MarginsType.DEFAULT, |
| valid: true, |
| available: true, |
| setByPolicy: false, |
| setFromUi: false, |
| key: 'marginsType', |
| updatesPreview: true, |
| }, |
| customMargins: { |
| value: {}, |
| unavailableValue: {}, |
| valid: true, |
| available: true, |
| setByPolicy: false, |
| setFromUi: false, |
| key: 'customMargins', |
| updatesPreview: true, |
| }, |
| dpi: { |
| value: {}, |
| unavailableValue: {}, |
| valid: true, |
| available: true, |
| setByPolicy: false, |
| setFromUi: false, |
| key: 'dpi', |
| updatesPreview: false, |
| }, |
| scaling: { |
| value: '100', |
| unavailableValue: '100', |
| valid: true, |
| available: true, |
| setByPolicy: false, |
| setFromUi: false, |
| key: 'scaling', |
| updatesPreview: true, |
| }, |
| scalingType: { |
| value: ScalingType.DEFAULT, |
| unavailableValue: ScalingType.DEFAULT, |
| valid: true, |
| available: true, |
| setByPolicy: false, |
| setFromUi: false, |
| key: 'scalingType', |
| updatesPreview: true, |
| }, |
| scalingTypePdf: { |
| value: ScalingType.DEFAULT, |
| unavailableValue: ScalingType.DEFAULT, |
| valid: true, |
| available: true, |
| setByPolicy: false, |
| setFromUi: false, |
| key: 'scalingTypePdf', |
| updatesPreview: true, |
| }, |
| duplex: { |
| value: true, |
| unavailableValue: false, |
| valid: true, |
| available: true, |
| setByPolicy: false, |
| setFromUi: false, |
| key: 'isDuplexEnabled', |
| updatesPreview: false, |
| }, |
| duplexShortEdge: { |
| value: false, |
| unavailableValue: false, |
| valid: true, |
| available: true, |
| setByPolicy: false, |
| setFromUi: false, |
| key: 'isDuplexShortEdge', |
| updatesPreview: false, |
| }, |
| cssBackground: { |
| value: false, |
| unavailableValue: false, |
| valid: true, |
| available: true, |
| setByPolicy: false, |
| setFromUi: false, |
| key: 'isCssBackgroundEnabled', |
| updatesPreview: true, |
| }, |
| selectionOnly: { |
| value: false, |
| unavailableValue: false, |
| valid: true, |
| available: true, |
| setByPolicy: false, |
| setFromUi: false, |
| key: '', |
| updatesPreview: true, |
| }, |
| headerFooter: { |
| value: true, |
| unavailableValue: false, |
| valid: true, |
| available: true, |
| setByPolicy: false, |
| setFromUi: false, |
| key: 'isHeaderFooterEnabled', |
| updatesPreview: true, |
| }, |
| rasterize: { |
| value: false, |
| unavailableValue: false, |
| valid: true, |
| available: true, |
| setByPolicy: false, |
| setFromUi: false, |
| key: '', |
| updatesPreview: true, |
| }, |
| vendorItems: { |
| value: {}, |
| unavailableValue: {}, |
| valid: true, |
| available: true, |
| setByPolicy: false, |
| setFromUi: false, |
| key: 'vendorOptions', |
| updatesPreview: false, |
| }, |
| pagesPerSheet: { |
| value: 1, |
| unavailableValue: 1, |
| valid: true, |
| available: true, |
| setByPolicy: false, |
| setFromUi: false, |
| key: '', |
| updatesPreview: true, |
| }, |
| // This does not represent a real setting value, and is used only to |
| // expose the availability of the other options settings section. |
| otherOptions: { |
| value: null, |
| unavailableValue: null, |
| valid: true, |
| available: true, |
| setByPolicy: false, |
| setFromUi: false, |
| key: '', |
| updatesPreview: false, |
| }, |
| // This does not represent a real settings value, but is used to |
| // propagate the correctly formatted ranges for print tickets. |
| ranges: { |
| value: [], |
| unavailableValue: [], |
| valid: true, |
| available: true, |
| setByPolicy: false, |
| setFromUi: false, |
| key: '', |
| updatesPreview: true, |
| }, |
| recentDestinations: { |
| value: [], |
| unavailableValue: [], |
| valid: true, |
| available: true, |
| setByPolicy: false, |
| setFromUi: false, |
| key: 'recentDestinations', |
| updatesPreview: false, |
| }, |
| // <if expr="is_chromeos"> |
| pin: { |
| value: false, |
| unavailableValue: false, |
| valid: true, |
| available: true, |
| setByPolicy: false, |
| setFromUi: false, |
| key: 'isPinEnabled', |
| updatesPreview: false, |
| }, |
| pinValue: { |
| value: '', |
| unavailableValue: '', |
| valid: true, |
| available: true, |
| setByPolicy: false, |
| setFromUi: false, |
| key: 'pinValue', |
| updatesPreview: false, |
| }, |
| // </if> |
| }; |
| }, |
| }, |
| |
| settingsManaged: { |
| type: Boolean, |
| notify: true, |
| value: false, |
| }, |
| |
| destination: Object, |
| |
| documentSettings: Object, |
| |
| margins: Object, |
| |
| pageSize: Object, |
| |
| maxSheets: { |
| type: Number, |
| value: 0, |
| notify: true, |
| }, |
| }; |
| } |
| |
| static get observers() { |
| return [ |
| 'updateSettingsFromDestination_(destination.capabilities)', |
| 'updateSettingsAvailabilityFromDocumentSettings_(' + |
| 'documentSettings.isModifiable, documentSettings.isFromArc,' + |
| 'documentSettings.hasCssMediaStyles, documentSettings.hasSelection)', |
| 'updateHeaderFooterAvailable_(' + |
| 'margins, settings.margins.value, settings.mediaSize.value)', |
| |
| ]; |
| } |
| |
| settings: Settings; |
| settingsManaged: boolean; |
| destination: Destination; |
| documentSettings: DocumentSettings; |
| margins: Margins; |
| pageSize: Size; |
| maxSheets: number; |
| |
| private initialized_: boolean = false; |
| private stickySettings_: SerializedSettings|null = null; |
| private policySettings_: PolicySettings|null = null; |
| private lastDestinationCapabilities_: Cdd|null = null; |
| |
| override connectedCallback() { |
| super.connectedCallback(); |
| |
| assert(!instance); |
| instance = this; |
| whenReadyResolver.resolve(); |
| } |
| |
| override disconnectedCallback() { |
| super.disconnectedCallback(); |
| |
| instance = null; |
| whenReadyResolver = new PromiseResolver(); |
| } |
| |
| private fire_(eventName: string, detail?: any) { |
| this.dispatchEvent( |
| new CustomEvent(eventName, {bubbles: true, composed: true, detail})); |
| } |
| |
| getSetting(settingName: string): Setting { |
| const setting = (this.get(settingName, this.settings) as Setting); |
| assert(setting, 'Setting is missing: ' + settingName); |
| return setting; |
| } |
| |
| /** |
| * @param settingName Name of the setting to get the value for. |
| * @return The value of the setting, accounting for availability. |
| */ |
| getSettingValue(settingName: string): any { |
| const setting = this.getSetting(settingName); |
| return setting.available ? setting.value : setting.unavailableValue; |
| } |
| |
| /** |
| * Updates settings.settingPath to |value|. Fires a preview-setting-changed |
| * event if the modification results in a change to the value returned by |
| * getSettingValue(). |
| */ |
| private setSettingPath_(settingPath: string, value: any) { |
| const settingName = settingPath.split('.')[0]; |
| const setting = this.getSetting(settingName); |
| const oldValue = this.getSettingValue(settingName); |
| this.set(`settings.${settingPath}`, value); |
| const newValue = this.getSettingValue(settingName); |
| if (newValue !== oldValue && setting.updatesPreview) { |
| this.fire_('preview-setting-changed'); |
| } |
| } |
| |
| /** |
| * Sets settings.settingName.value to |value|, unless updating the setting is |
| * disallowed by enterprise policy. Fires preview-setting-changed and |
| * sticky-setting-changed events if the update impacts the preview or requires |
| * an update to sticky settings. Used for setting settings from UI elements. |
| * @param settingName Name of the setting to set |
| * @param value The value to set the setting to. |
| * @param noSticky Whether to avoid stickying the setting. Defaults to false. |
| */ |
| setSetting(settingName: string, value: any, noSticky?: boolean) { |
| const setting = this.getSetting(settingName); |
| if (setting.setByPolicy) { |
| return; |
| } |
| const fireStickyEvent = !noSticky && setting.value !== value && setting.key; |
| this.setSettingPath_(`${settingName}.value`, value); |
| if (!noSticky) { |
| this.setSettingPath_(`${settingName}.setFromUi`, true); |
| } |
| if (fireStickyEvent && this.initialized_) { |
| this.fire_('sticky-setting-changed', this.getStickySettings_()); |
| } |
| } |
| |
| /** |
| * @param settingName Name of the setting to set |
| * @param start |
| * @param end |
| * @param newValue The value to add (if any). |
| * @param noSticky Whether to avoid stickying the setting. Defaults to false. |
| */ |
| setSettingSplice( |
| settingName: string, start: number, end: number, newValue: any, |
| noSticky?: boolean) { |
| const setting = this.getSetting(settingName); |
| if (setting.setByPolicy) { |
| return; |
| } |
| if (newValue) { |
| this.splice(`settings.${settingName}.value`, start, end, newValue); |
| } else { |
| this.splice(`settings.${settingName}.value`, start, end); |
| } |
| if (!noSticky) { |
| this.setSettingPath_(`${settingName}.setFromUi`, true); |
| } |
| if (!noSticky && setting.key && this.initialized_) { |
| this.fire_('sticky-setting-changed', this.getStickySettings_()); |
| } |
| } |
| |
| /** |
| * Sets the validity of |settingName| to |valid|. If the validity is changed, |
| * fires a setting-valid-changed event. |
| * @param settingName Name of the setting to set |
| * @param valid Whether the setting value is currently valid. |
| */ |
| setSettingValid(settingName: string, valid: boolean) { |
| const setting = this.getSetting(settingName); |
| // Should not set the setting to invalid if it is not available, as there |
| // is no way for the user to change the value in this case. |
| if (!valid) { |
| assert(setting.available, 'Setting is not available: ' + settingName); |
| } |
| const shouldFireEvent = valid !== setting.valid; |
| this.set(`settings.${settingName}.valid`, valid); |
| if (shouldFireEvent) { |
| this.fire_('setting-valid-changed', valid); |
| } |
| } |
| |
| /** |
| * Updates the availability of the settings sections and values of dpi and |
| * media size settings based on the destination capabilities. |
| */ |
| private updateSettingsFromDestination_() { |
| if (!this.destination || !this.settings) { |
| return; |
| } |
| |
| if (this.destination.capabilities === this.lastDestinationCapabilities_) { |
| return; |
| } |
| |
| this.lastDestinationCapabilities_ = this.destination.capabilities; |
| |
| const caps = this.destination.capabilities ? |
| this.destination.capabilities.printer : |
| null; |
| this.updateSettingsAvailabilityFromDestination_(caps); |
| |
| if (!caps) { |
| return; |
| } |
| |
| this.updateSettingsValues_(caps); |
| this.applyPersistentCddDefaults_(); |
| } |
| |
| private updateSettingsAvailabilityFromDestination_(caps: CddCapabilities| |
| null) { |
| this.setSettingPath_( |
| 'copies.available', this.destination.hasCopiesCapability); |
| this.setSettingPath_('collate.available', !!caps && !!caps.collate); |
| this.setSettingPath_( |
| 'color.available', this.destination.hasColorCapability); |
| |
| const capsHasDuplex = !!caps && !!caps.duplex && !!caps.duplex.option; |
| const capsHasLongEdge = capsHasDuplex && |
| caps!.duplex!.option.some(o => o.type === DuplexType.LONG_EDGE); |
| const capsHasShortEdge = capsHasDuplex && |
| caps!.duplex!.option.some(o => o.type === DuplexType.SHORT_EDGE); |
| this.setSettingPath_( |
| 'duplexShortEdge.available', capsHasLongEdge && capsHasShortEdge); |
| this.setSettingPath_( |
| 'duplex.available', |
| (capsHasLongEdge || capsHasShortEdge) && |
| caps!.duplex!.option.some(o => o.type === DuplexType.NO_DUPLEX)); |
| |
| this.setSettingPath_( |
| 'vendorItems.available', !!caps && !!caps.vendor_capability); |
| |
| // <if expr="is_chromeos"> |
| const pinSupported = !!caps && !!caps.pin && !!caps.pin.supported && |
| loadTimeData.getBoolean('isEnterpriseManaged'); |
| this.set('settings.pin.available', pinSupported); |
| this.set('settings.pinValue.available', pinSupported); |
| // </if> |
| |
| if (this.documentSettings) { |
| this.updateSettingsAvailabilityFromDestinationAndDocumentSettings_(); |
| } |
| } |
| |
| private updateSettingsAvailabilityFromDestinationAndDocumentSettings_() { |
| const isSaveAsPDF = this.destination.type === PrinterType.PDF_PRINTER; |
| const knownSizeToSaveAsPdf = isSaveAsPDF && |
| (!this.documentSettings.isModifiable || |
| this.documentSettings.hasCssMediaStyles); |
| const scalingAvailable = |
| !knownSizeToSaveAsPdf && !this.documentSettings.isFromArc; |
| this.setSettingPath_('scaling.available', scalingAvailable); |
| this.setSettingPath_( |
| 'scalingType.available', |
| scalingAvailable && this.documentSettings.isModifiable); |
| this.setSettingPath_( |
| 'scalingTypePdf.available', |
| scalingAvailable && !this.documentSettings.isModifiable); |
| const caps = this.destination && this.destination.capabilities ? |
| this.destination.capabilities.printer : |
| null; |
| this.setSettingPath_( |
| 'mediaSize.available', |
| !!caps && !!caps.media_size && !knownSizeToSaveAsPdf); |
| this.setSettingPath_( |
| 'dpi.available', |
| !this.documentSettings.isFromArc && !!caps && !!caps.dpi && |
| !!caps.dpi.option && caps.dpi.option.length > 1); |
| this.setSettingPath_('layout.available', this.isLayoutAvailable_(caps)); |
| } |
| |
| private updateSettingsAvailabilityFromDocumentSettings_() { |
| if (!this.settings) { |
| return; |
| } |
| |
| this.setSettingPath_( |
| 'pagesPerSheet.available', !this.documentSettings.isFromArc); |
| this.setSettingPath_( |
| 'margins.available', |
| !this.documentSettings.isFromArc && this.documentSettings.isModifiable); |
| this.setSettingPath_( |
| 'customMargins.available', |
| !this.documentSettings.isFromArc && this.documentSettings.isModifiable); |
| this.setSettingPath_( |
| 'cssBackground.available', |
| !this.documentSettings.isFromArc && this.documentSettings.isModifiable); |
| this.setSettingPath_( |
| 'selectionOnly.available', |
| !this.documentSettings.isFromArc && |
| this.documentSettings.isModifiable && |
| this.documentSettings.hasSelection); |
| this.setSettingPath_( |
| 'headerFooter.available', |
| !this.documentSettings.isFromArc && this.isHeaderFooterAvailable_()); |
| this.setSettingPath_( |
| 'rasterize.available', |
| !this.documentSettings.isFromArc && this.isRasterizeAvailable_()); |
| this.setSettingPath_( |
| 'otherOptions.available', |
| this.settings.cssBackground.available || |
| this.settings.selectionOnly.available || |
| this.settings.headerFooter.available || |
| this.settings.rasterize.available); |
| |
| if (this.destination) { |
| this.updateSettingsAvailabilityFromDestinationAndDocumentSettings_(); |
| } |
| } |
| |
| private updateHeaderFooterAvailable_() { |
| if (this.documentSettings === undefined) { |
| return; |
| } |
| |
| this.setSettingPath_( |
| 'headerFooter.available', this.isHeaderFooterAvailable_()); |
| } |
| |
| /** |
| * @return Whether the header/footer setting should be available. |
| */ |
| private isHeaderFooterAvailable_(): boolean { |
| // Always unavailable for PDFs. |
| if (!this.documentSettings.isModifiable) { |
| return false; |
| } |
| |
| // Always unavailable for small paper sizes. |
| const microns = this.getSettingValue('layout') ? |
| this.getSettingValue('mediaSize').width_microns : |
| this.getSettingValue('mediaSize').height_microns; |
| if (microns < MINIMUM_HEIGHT_MICRONS) { |
| return false; |
| } |
| |
| // Otherwise, availability depends on the margins. |
| const marginsType = this.getSettingValue('margins') as MarginsType; |
| if (marginsType === MarginsType.NO_MARGINS) { |
| return false; |
| } |
| |
| if (marginsType === MarginsType.MINIMUM) { |
| return true; |
| } |
| |
| return !this.margins || |
| this.margins.get(CustomMarginsOrientation.TOP) > 0 || |
| this.margins.get(CustomMarginsOrientation.BOTTOM) > 0; |
| } |
| |
| private updateRasterizeAvailable_() { |
| // Need document settings to know if source is PDF. |
| if (this.documentSettings === undefined) { |
| return; |
| } |
| |
| this.setSettingPath_('rasterize.available', this.isRasterizeAvailable_()); |
| } |
| |
| /** |
| * @return Whether the rasterization setting should be available. |
| */ |
| private isRasterizeAvailable_(): boolean { |
| // Only a possibility for PDFs. Always available for PDFs on Linux and |
| // ChromeOS. crbug.com/675798 |
| let available = |
| !!this.documentSettings && !this.documentSettings.isModifiable; |
| |
| // <if expr="is_win or is_macosx"> |
| // Availability on Windows or macOS depends upon policy. |
| if (!available || !this.policySettings_) { |
| return false; |
| } |
| const policy = this.policySettings_['printPdfAsImageAvailability']; |
| available = policy !== undefined && policy.value; |
| // </if> |
| |
| return available; |
| } |
| |
| private isLayoutAvailable_(caps: CddCapabilities|null): boolean { |
| if (!caps || !caps.page_orientation || !caps.page_orientation.option || |
| (!this.documentSettings.isModifiable && |
| !this.documentSettings.isFromArc) || |
| this.documentSettings.hasCssMediaStyles) { |
| return false; |
| } |
| let hasAutoOrPortraitOption = false; |
| let hasLandscapeOption = false; |
| caps.page_orientation.option.forEach(option => { |
| hasAutoOrPortraitOption = hasAutoOrPortraitOption || |
| option.type === 'AUTO' || option.type === 'PORTRAIT'; |
| hasLandscapeOption = hasLandscapeOption || option.type === 'LANDSCAPE'; |
| }); |
| return hasLandscapeOption && hasAutoOrPortraitOption; |
| } |
| |
| private updateSettingsValues_(caps: CddCapabilities|null) { |
| if (this.settings.mediaSize.available) { |
| const defaultOption = |
| caps!.media_size!.option.find(o => !!o.is_default) || |
| caps!.media_size!.option[0]; |
| let matchingOption = null; |
| // If the setting does not have a valid value, the UI has just started so |
| // do not try to get a matching value; just set the printer default in |
| // case the user doesn't have sticky settings. |
| if (this.settings.mediaSize.setFromUi) { |
| const currentMediaSize = this.getSettingValue('mediaSize'); |
| matchingOption = caps!.media_size!.option.find(o => { |
| return o.height_microns === currentMediaSize.height_microns && |
| o.width_microns === currentMediaSize.width_microns; |
| }); |
| } |
| this.setSetting('mediaSize', matchingOption || defaultOption, true); |
| } |
| |
| if (this.settings.dpi.available) { |
| const defaultOption = |
| caps!.dpi!.option.find(o => !!o.is_default) || caps!.dpi!.option[0]; |
| let matchingOption = null; |
| if (this.settings.dpi.setFromUi) { |
| const currentDpi = this.getSettingValue('dpi'); |
| matchingOption = caps!.dpi!.option.find(o => { |
| return o.horizontal_dpi === currentDpi.horizontal_dpi && |
| o.vertical_dpi === currentDpi.vertical_dpi; |
| }); |
| } |
| this.setSetting('dpi', matchingOption || defaultOption, true); |
| } else if ( |
| caps && caps.dpi && caps.dpi.option && caps.dpi.option.length > 0) { |
| const unavailableValue = |
| caps!.dpi!.option.find(o => !!o.is_default) || caps!.dpi!.option[0]; |
| this.setSettingPath_('dpi.unavailableValue', unavailableValue); |
| } |
| |
| if (!this.settings.color.setFromUi && this.settings.color.available) { |
| const defaultOption = this.destination.defaultColorOption; |
| if (defaultOption) { |
| this.setSetting( |
| 'color', |
| !['STANDARD_MONOCHROME', 'CUSTOM_MONOCHROME'].includes( |
| defaultOption.type!), |
| true); |
| } |
| } else if ( |
| !this.settings.color.available && caps && caps.color && |
| caps.color.option && caps.color.option.length > 0) { |
| this.setSettingPath_( |
| 'color.unavailableValue', |
| !['STANDARD_MONOCHROME', 'CUSTOM_MONOCHROME'].includes( |
| caps.color.option[0].type!)); |
| } else if (!this.settings.color.available) { |
| // if no color capability is reported, assume black and white. |
| this.setSettingPath_('color.unavailableValue', false); |
| } |
| |
| if (!this.settings.duplex.setFromUi && this.settings.duplex.available) { |
| const defaultOption = caps!.duplex!.option.find(o => !!o.is_default); |
| this.setSetting( |
| 'duplex', |
| defaultOption ? (defaultOption.type === DuplexType.LONG_EDGE || |
| defaultOption.type === DuplexType.SHORT_EDGE) : |
| false, |
| true); |
| this.setSetting( |
| 'duplexShortEdge', |
| defaultOption ? defaultOption.type === DuplexType.SHORT_EDGE : false, |
| true); |
| |
| if (!this.settings.duplexShortEdge.available) { |
| // Duplex is available, so must have only one two sided printing option. |
| // Set duplexShortEdge's unavailable value based on the printer. |
| this.setSettingPath_( |
| 'duplexShortEdge.unavailableValue', |
| caps!.duplex!.option.some(o => o.type === DuplexType.SHORT_EDGE)); |
| } |
| } else if ( |
| !this.settings.duplex.available && caps && caps.duplex && |
| caps.duplex.option) { |
| // In this case, there must only be one option. |
| const hasLongEdge = |
| caps!.duplex!.option.some(o => o.type === DuplexType.LONG_EDGE); |
| const hasShortEdge = |
| caps!.duplex!.option.some(o => o.type === DuplexType.SHORT_EDGE); |
| // If the only option available is long edge, the value should always be |
| // true. |
| this.setSettingPath_( |
| 'duplex.unavailableValue', hasLongEdge || hasShortEdge); |
| this.setSettingPath_('duplexShortEdge.unavailableValue', hasShortEdge); |
| } else if (!this.settings.duplex.available) { |
| // If no duplex capability is reported, assume false. |
| this.setSettingPath_('duplex.unavailableValue', false); |
| this.setSettingPath_('duplexShortEdge.unavailableValue', false); |
| } |
| |
| if (this.settings.vendorItems.available) { |
| const vendorSettings: {[key: string]: any} = {}; |
| for (const item of caps!.vendor_capability!) { |
| let defaultValue = null; |
| if (item.type === 'SELECT' && item.select_cap && |
| item.select_cap.option) { |
| const defaultOption = |
| item.select_cap.option.find(o => !!o.is_default); |
| defaultValue = defaultOption ? defaultOption.value : null; |
| } else if (item.type === 'RANGE') { |
| if (item.range_cap) { |
| defaultValue = item.range_cap.default || null; |
| } |
| } else if (item.type === 'TYPED_VALUE') { |
| if (item.typed_value_cap) { |
| defaultValue = item.typed_value_cap.default || null; |
| } |
| } |
| if (defaultValue !== null) { |
| vendorSettings[item.id] = defaultValue; |
| } |
| } |
| this.setSetting('vendorItems', vendorSettings, true); |
| } |
| } |
| |
| /** |
| * Caches the sticky settings and sets up the recent destinations. Sticky |
| * settings will be applied when destinaton capabilities have been retrieved. |
| */ |
| setStickySettings(savedSettingsStr: string|null) { |
| assert(!this.stickySettings_); |
| |
| if (!savedSettingsStr) { |
| return; |
| } |
| |
| let savedSettings; |
| try { |
| savedSettings = JSON.parse(savedSettingsStr) as SerializedSettings; |
| } catch (e) { |
| console.warn('Unable to parse state ' + e); |
| return; // use default values rather than updating. |
| } |
| if (savedSettings.version !== 2) { |
| return; |
| } |
| |
| let recentDestinations = savedSettings.recentDestinations || []; |
| if (!Array.isArray(recentDestinations)) { |
| recentDestinations = [recentDestinations]; |
| } |
| |
| // Remove unsupported privet and cloud printers from the sticky settings, |
| // to free up these spots for supported printers. |
| const unsupportedOrigins: DestinationOrigin[] = [ |
| DestinationOrigin.COOKIES, |
| // <if expr="is_chromeos"> |
| DestinationOrigin.DEVICE, |
| // </if> |
| DestinationOrigin.PRIVET, |
| ]; |
| recentDestinations = recentDestinations.filter((d: RecentDestination) => { |
| return !unsupportedOrigins.includes(d.origin); |
| }); |
| |
| // Initialize recent destinations early so that the destination store can |
| // start trying to fetch them. |
| this.setSetting('recentDestinations', recentDestinations); |
| savedSettings.recentDestinations = recentDestinations; |
| |
| this.stickySettings_ = savedSettings; |
| } |
| |
| /** |
| * Helper function for configurePolicySetting_(). Sets value and managed flag |
| * for given setting. |
| * @param settingName Name of the setting being applied. |
| * @param value Value of the setting provided via policy. |
| * @param managed Flag showing whether value of setting is managed. |
| * @param applyOnDestinationUpdate Flag showing whether policy |
| * should be applied on every destination update. |
| */ |
| private setPolicySetting_( |
| settingName: string, value: any, managed: boolean, |
| applyOnDestinationUpdate: boolean) { |
| if (!this.policySettings_) { |
| this.policySettings_ = {}; |
| } |
| (this.policySettings_ as {[key: string]: PolicyEntry})[settingName] = { |
| value: value, |
| managed: managed, |
| applyOnDestinationUpdate: applyOnDestinationUpdate, |
| }; |
| } |
| |
| /** |
| * Helper function for setPolicySettings(). Calculates value and managed flag |
| * of the setting according to allowed and default modes. |
| */ |
| private configurePolicySetting_( |
| settingName: string, allowedMode: any, defaultMode: any) { |
| switch (settingName) { |
| case 'headerFooter': { |
| const value = allowedMode !== undefined ? allowedMode : defaultMode; |
| if (value !== undefined) { |
| this.setPolicySetting_( |
| settingName, value, allowedMode !== undefined, |
| /*applyOnDestinationUpdate=*/ false); |
| } |
| break; |
| } |
| case 'cssBackground': { |
| const value = allowedMode ? allowedMode : defaultMode; |
| if (value !== undefined) { |
| this.setPolicySetting_( |
| settingName, value === BackgroundGraphicsModeRestriction.ENABLED, |
| !!allowedMode, /*applyOnDestinationUpdate=*/ false); |
| } |
| break; |
| } |
| case 'mediaSize': { |
| if (defaultMode !== undefined) { |
| this.setPolicySetting_( |
| settingName, defaultMode, /*managed=*/ false, |
| /*applyOnDestinationUpdate=*/ true); |
| } |
| break; |
| } |
| case 'color': { |
| const value = allowedMode ? allowedMode : defaultMode; |
| if (value !== undefined) { |
| this.setPolicySetting_( |
| settingName, value, !!allowedMode, |
| /*applyOnDestinationUpdate=*/ false); |
| } |
| break; |
| } |
| case 'duplex': { |
| const value = allowedMode ? allowedMode : defaultMode; |
| if (value !== undefined) { |
| this.setPolicySetting_( |
| settingName, value, !!allowedMode, |
| /*applyOnDestinationUpdate=*/ false); |
| } |
| break; |
| } |
| case 'pin': { |
| const value = allowedMode ? allowedMode : defaultMode; |
| if (value !== undefined) { |
| this.setPolicySetting_( |
| settingName, value, !!allowedMode, |
| /*applyOnDestinationUpdate=*/ false); |
| } |
| break; |
| } |
| case 'printPdfAsImageAvailability': { |
| const value = allowedMode !== undefined ? allowedMode : defaultMode; |
| if (value !== undefined) { |
| this.setPolicySetting_( |
| settingName, value, /*managed=*/ false, |
| /*applyOnDestinationUpdate=*/ false); |
| } |
| break; |
| } |
| case 'printPdfAsImage': { |
| if (defaultMode !== undefined) { |
| this.setPolicySetting_( |
| settingName, defaultMode, /*managed=*/ false, |
| /*applyOnDestinationUpdate=*/ false); |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| |
| /** |
| * Sets settings in accordance to policies from native code, and prevents |
| * those settings from being changed via other means. |
| */ |
| setPolicySettings(policies: Policies|undefined) { |
| if (policies === undefined) { |
| return; |
| } |
| const policiesObject = policies as {[key: string]: PolicyObjectEntry}; |
| ['headerFooter', 'cssBackground', 'mediaSize'].forEach(settingName => { |
| if (!policiesObject[settingName]) { |
| return; |
| } |
| const defaultMode = policiesObject[settingName].defaultMode; |
| const allowedMode = policiesObject[settingName].allowedMode; |
| this.configurePolicySetting_(settingName, allowedMode, defaultMode); |
| }); |
| // <if expr="is_chromeos"> |
| if (policiesObject['sheets']) { |
| if (!this.policySettings_) { |
| this.policySettings_ = {}; |
| } |
| this.policySettings_['sheets'] = { |
| value: policiesObject['sheets'].value, |
| applyOnDestinationUpdate: false, |
| managed: true, |
| }; |
| } |
| ['color', 'duplex', 'pin'].forEach(settingName => { |
| if (!policiesObject[settingName]) { |
| return; |
| } |
| const defaultMode = policiesObject[settingName].defaultMode; |
| const allowedMode = policiesObject[settingName].allowedMode; |
| this.configurePolicySetting_(settingName, allowedMode, defaultMode); |
| }); |
| // </if> |
| // <if expr="is_win or is_macosx"> |
| if (policies['printPdfAsImageAvailability']) { |
| if (!this.policySettings_) { |
| this.policySettings_ = {}; |
| } |
| const allowedMode = policies['printPdfAsImageAvailability'].allowedMode; |
| this.configurePolicySetting_( |
| 'printPdfAsImageAvailability', allowedMode, /*defaultMode=*/ false); |
| } |
| // </if> |
| if (policies['printPdfAsImage']) { |
| if (!this.policySettings_) { |
| this.policySettings_ = {}; |
| } |
| const defaultMode = policies['printPdfAsImage'].defaultMode; |
| this.configurePolicySetting_( |
| 'printPdfAsImage', /*allowedMode=*/ undefined, defaultMode); |
| } |
| } |
| |
| applyStickySettings() { |
| if (this.stickySettings_) { |
| STICKY_SETTING_NAMES.forEach(settingName => { |
| const setting = this.get(settingName, this.settings) as Setting; |
| const value = |
| (this.stickySettings_ as {[key: string]: any})[setting.key]; |
| if (value !== undefined) { |
| this.setSetting(settingName, value); |
| } else { |
| this.applyScalingStickySettings_(settingName); |
| } |
| }); |
| } |
| this.applyPersistentCddDefaults_(); |
| this.applyPolicySettings_(); |
| this.initialized_ = true; |
| this.updateManaged_(); |
| this.stickySettings_ = null; |
| this.fire_('sticky-setting-changed', this.getStickySettings_()); |
| } |
| |
| /** |
| * Helper function for applyStickySettings(). Checks if the setting |
| * is a scaling setting and applies by applying the old types |
| * that rely on 'fitToPage' and 'customScaling'. |
| * @param settingName Name of the setting being applied. |
| */ |
| private applyScalingStickySettings_(settingName: string) { |
| // TODO(dhoss): Remove checks for 'customScaling' and 'fitToPage' |
| if (settingName === 'scalingType' && |
| 'customScaling' in this.stickySettings_!) { |
| const isCustom = this.stickySettings_['customScaling']; |
| const scalingType = isCustom ? ScalingType.CUSTOM : ScalingType.DEFAULT; |
| this.setSetting(settingName, scalingType); |
| } else if (settingName === 'scalingTypePdf') { |
| if ('isFitToPageEnabled' in this.stickySettings_!) { |
| const isFitToPage = this.stickySettings_['isFitToPageEnabled']; |
| const scalingTypePdf = isFitToPage ? |
| ScalingType.FIT_TO_PAGE : |
| this.getSetting('scalingType').value; |
| this.setSetting(settingName, scalingTypePdf); |
| } else if (this.getSetting('scalingType').value === ScalingType.CUSTOM) { |
| // In the event that 'isFitToPageEnabled' was not in the sticky |
| // settings, and 'scalingType' has been set to custom, we want |
| // 'scalingTypePdf' to match. |
| this.setSetting(settingName, ScalingType.CUSTOM); |
| } |
| } |
| } |
| |
| private applyPolicySettings_() { |
| if (this.policySettings_) { |
| for (const [settingName, policy] of Object.entries( |
| this.policySettings_)) { |
| const policyEntry = policy as PolicyEntry; |
| // <if expr="is_chromeos"> |
| if (settingName === 'sheets') { |
| this.maxSheets = policyEntry.value; |
| continue; |
| } |
| if (settingName === 'color') { |
| this.set( |
| 'settings.color.value', |
| policyEntry.value === ColorModeRestriction.COLOR); |
| this.set('settings.color.setByPolicy', policyEntry.managed); |
| continue; |
| } |
| if (settingName === 'duplex') { |
| let setDuplexTypeByPolicy = false; |
| this.set( |
| 'settings.duplex.value', |
| policyEntry.value !== DuplexModeRestriction.SIMPLEX); |
| if (policyEntry.value === DuplexModeRestriction.SHORT_EDGE) { |
| this.set('settings.duplexShortEdge.value', true); |
| setDuplexTypeByPolicy = true; |
| } else if (policyEntry.value === DuplexModeRestriction.LONG_EDGE) { |
| this.set('settings.duplexShortEdge.value', false); |
| setDuplexTypeByPolicy = true; |
| } |
| this.set('settings.duplex.setByPolicy', policyEntry.managed); |
| this.set( |
| 'settings.duplexShortEdge.setByPolicy', |
| policyEntry.managed && setDuplexTypeByPolicy); |
| continue; |
| } |
| if (settingName === 'pin') { |
| if (policyEntry.value === PinModeRestriction.NO_PIN && |
| policyEntry.managed) { |
| this.set('settings.pin.available', false); |
| this.set('settings.pinValue.available', false); |
| } else { |
| this.set( |
| 'settings.pin.value', |
| policyEntry.value === PinModeRestriction.PIN); |
| } |
| this.set('settings.pin.setByPolicy', policyEntry.managed); |
| continue; |
| } |
| // </if> |
| // <if expr="is_win or is_macosx"> |
| if (settingName === 'printPdfAsImageAvailability') { |
| this.updateRasterizeAvailable_(); |
| if (this.settings.rasterize.available) { |
| // If rasterize is available then otherOptions must be available. |
| this.setSettingPath_('otherOptions.available', true); |
| } |
| continue; |
| } |
| // </if> |
| if (settingName === 'printPdfAsImage') { |
| if (policyEntry.value) { |
| this.setSetting('rasterize', policyEntry.value, true); |
| } |
| continue; |
| } |
| if (policyEntry.value !== undefined && |
| !policyEntry.applyOnDestinationUpdate) { |
| this.setSetting(settingName, policyEntry.value, true); |
| if (policyEntry.managed) { |
| this.set(`settings.${settingName}.setByPolicy`, true); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * If the setting has a default value specified in the CDD capabilities and |
| * the attribute `reset_to_default` is true, this method will return the |
| * default value for the setting; otherwise it will return null. |
| */ |
| private getResetValue_(capability: CapabilityWithReset): (object|null) { |
| if (!capability.reset_to_default) { |
| return null; |
| } |
| const cddDefault = capability.option.find(o => !!o.is_default); |
| if (!cddDefault) { |
| return null; |
| } |
| return cddDefault; |
| } |
| |
| /** |
| * For PrinterProvider printers, it's possible to specify for a setting to |
| * always reset to the default value using the `reset_to_default` attribute. |
| * If `reset_to_default` is true and a default value for the |
| * setting is specified, this method will reset the setting |
| * value to the default value. |
| */ |
| private applyPersistentCddDefaults_() { |
| if (!this.destination || !this.destination.isExtension) { |
| return; |
| } |
| |
| const caps = this.destination && this.destination.capabilities ? |
| this.destination.capabilities.printer : |
| null; |
| if (!caps) { |
| return; |
| } |
| |
| if (this.settings.mediaSize.available) { |
| const cddDefault = this.getResetValue_(caps['media_size']!); |
| if (cddDefault) { |
| this.set('settings.mediaSize.value', cddDefault); |
| } |
| } |
| |
| if (this.settings.color.available) { |
| const cddDefault = this.getResetValue_(caps['color']!) as ColorOption; |
| if (cddDefault) { |
| this.set( |
| 'settings.color.value', |
| !['STANDARD_MONOCHROME', 'CUSTOM_MONOCHROME'].includes( |
| cddDefault.type!)); |
| } |
| } |
| |
| if (this.settings.duplex.available) { |
| const cddDefault = this.getResetValue_(caps['duplex']!) as DuplexOption; |
| if (cddDefault) { |
| this.set( |
| 'settings.duplex.value', |
| cddDefault.type === DuplexType.LONG_EDGE || |
| cddDefault.type === DuplexType.SHORT_EDGE); |
| if (!this.settings.duplexShortEdge.available) { |
| this.set( |
| 'settings.duplexShortEdge.value', |
| cddDefault.type === DuplexType.SHORT_EDGE); |
| } |
| } |
| } |
| |
| if (this.settings.dpi.available) { |
| const cddDefault = this.getResetValue_(caps['dpi']!); |
| if (cddDefault) { |
| this.set('settings.dpi.value', cddDefault); |
| } |
| } |
| } |
| |
| /** |
| * Restricts settings and applies defaults as defined by policy applicable to |
| * current destination. |
| */ |
| applyDestinationSpecificPolicies() { |
| if (this.settings.mediaSize.available && this.policySettings_) { |
| const mediaSizePolicy = this.policySettings_['mediaSize'] && |
| this.policySettings_['mediaSize'].value; |
| if (mediaSizePolicy !== undefined) { |
| const matchingOption = |
| this.destination.capabilities!.printer.media_size!.option.find( |
| o => { |
| return o.width_microns === mediaSizePolicy.width && |
| o.height_microns === mediaSizePolicy.height; |
| }); |
| if (matchingOption !== undefined) { |
| this.set('settings.mediaSize.value', matchingOption); |
| } |
| } |
| } |
| |
| this.updateManaged_(); |
| } |
| |
| private updateManaged_() { |
| let managedSettings = ['cssBackground', 'headerFooter']; |
| // <if expr="is_chromeos"> |
| managedSettings = |
| managedSettings.concat(['color', 'duplex', 'duplexShortEdge', 'pin']); |
| // </if> |
| this.settingsManaged = managedSettings.some(settingName => { |
| const setting = this.getSetting(settingName); |
| return setting.available && setting.setByPolicy; |
| }); |
| } |
| |
| initialized(): boolean { |
| return this.initialized_; |
| } |
| |
| private getStickySettings_(): string { |
| const serialization: {[key: string]: any} = {}; |
| serialization['version'] = 2; |
| |
| STICKY_SETTING_NAMES.forEach(settingName => { |
| const setting = this.get(settingName, this.settings); |
| if (setting.setFromUi) { |
| serialization[setting.key] = setting.value; |
| } |
| }); |
| |
| return JSON.stringify(serialization); |
| } |
| |
| private getDuplexMode_(): DuplexMode { |
| if (!this.getSettingValue('duplex')) { |
| return DuplexMode.SIMPLEX; |
| } |
| |
| return this.getSettingValue('duplexShortEdge') ? DuplexMode.SHORT_EDGE : |
| DuplexMode.LONG_EDGE; |
| } |
| |
| private getCddDuplexType_(): DuplexType { |
| if (!this.getSettingValue('duplex')) { |
| return DuplexType.NO_DUPLEX; |
| } |
| |
| return this.getSettingValue('duplexShortEdge') ? DuplexType.SHORT_EDGE : |
| DuplexType.LONG_EDGE; |
| } |
| |
| /** |
| * Creates a string that represents a print ticket. |
| * @param destination Destination to print to. |
| * @param openPdfInPreview Whether this print request is to open |
| * the PDF in Preview app (Mac only). |
| * @param showSystemDialog Whether this print request is to show |
| * the system dialog. |
| * @return Serialized print ticket. |
| */ |
| createPrintTicket( |
| destination: Destination, openPdfInPreview: boolean, |
| showSystemDialog: boolean): string { |
| const dpi = this.getSettingValue('dpi') as DpiOption; |
| const scalingSettingKey = this.getSetting('scalingTypePdf').available ? |
| 'scalingTypePdf' : |
| 'scalingType'; |
| const ticket: PrintTicket = { |
| mediaSize: this.getSettingValue('mediaSize') as MediaSizeValue, |
| pageCount: this.getSettingValue('pages').length, |
| landscape: this.getSettingValue('layout'), |
| color: destination.getNativeColorModel( |
| this.getSettingValue('color') as boolean), |
| headerFooterEnabled: false, // only used in print preview |
| marginsType: this.getSettingValue('margins'), |
| duplex: this.getDuplexMode_(), |
| copies: this.getSettingValue('copies'), |
| collate: this.getSettingValue('collate'), |
| shouldPrintBackgrounds: this.getSettingValue('cssBackground'), |
| shouldPrintSelectionOnly: false, // only used in print preview |
| previewModifiable: this.documentSettings.isModifiable, |
| printerType: destination.type, |
| rasterizePDF: this.getSettingValue('rasterize'), |
| scaleFactor: |
| this.getSettingValue(scalingSettingKey) === ScalingType.CUSTOM ? |
| parseInt(this.getSettingValue('scaling'), 10) : |
| 100, |
| scalingType: this.getSettingValue(scalingSettingKey), |
| pagesPerSheet: this.getSettingValue('pagesPerSheet'), |
| dpiHorizontal: (dpi && 'horizontal_dpi' in dpi) ? dpi.horizontal_dpi : 0, |
| dpiVertical: (dpi && 'vertical_dpi' in dpi) ? dpi.vertical_dpi : 0, |
| dpiDefault: (dpi && 'is_default' in dpi) ? dpi.is_default! : false, |
| deviceName: destination.id, |
| pageWidth: this.pageSize.width, |
| pageHeight: this.pageSize.height, |
| showSystemDialog: showSystemDialog, |
| // <if expr="is_chromeos"> |
| printToGoogleDrive: |
| destination.id === GooglePromotedDestinationId.SAVE_TO_DRIVE_CROS, |
| // </if> |
| }; |
| |
| if (openPdfInPreview) { |
| ticket['openPDFInPreview'] = openPdfInPreview; |
| } |
| |
| if (this.getSettingValue('margins') === MarginsType.CUSTOM) { |
| ticket['marginsCustom'] = this.getSettingValue('customMargins'); |
| } |
| |
| if (destination.isExtension) { |
| // TODO(rbpotter): Get local and PDF printers to use the same ticket and |
| // send only this ticket instead of nesting it in a larger ticket. |
| ticket['ticket'] = this.createCloudJobTicket(destination); |
| ticket['capabilities'] = JSON.stringify(destination.capabilities); |
| } |
| |
| // <if expr="is_chromeos"> |
| if (this.getSettingValue('pin')) { |
| ticket['pinValue'] = this.getSettingValue('pinValue'); |
| } |
| if (destination.origin === DestinationOrigin.CROS) { |
| ticket['advancedSettings'] = this.getSettingValue('vendorItems'); |
| } |
| // </if> |
| |
| return JSON.stringify(ticket); |
| } |
| |
| /** |
| * Creates an object that represents a Google Cloud Print print ticket. |
| * @param destination Destination to print to. |
| * @return Google Cloud Print print ticket. |
| */ |
| createCloudJobTicket(destination: Destination): string { |
| assert( |
| destination.isExtension, |
| 'Trying to create a Google Cloud Print print ticket for a local ' + |
| ' non-extension destination'); |
| assert( |
| destination.capabilities, |
| 'Trying to create a Google Cloud Print print ticket for a ' + |
| 'destination with no print capabilities'); |
| |
| // Create CJT (Cloud Job Ticket) |
| const cjt: CloudJobTicket = {version: '1.0', print: {}}; |
| if (this.settings.collate.available) { |
| cjt.print.collate = {collate: this.settings.collate.value}; |
| } |
| if (this.settings.color.available) { |
| const selectedOption = destination.getSelectedColorOption( |
| this.settings.color.value as boolean); |
| if (!selectedOption) { |
| console.warn('Could not find correct color option'); |
| } else { |
| cjt.print.color = {type: selectedOption.type}; |
| if (selectedOption.hasOwnProperty('vendor_id')) { |
| cjt.print.color!.vendor_id = selectedOption.vendor_id; |
| } |
| } |
| } else { |
| // Always try setting the color in the print ticket, otherwise a |
| // reasonable reader of the ticket will have to do more work, or process |
| // the ticket sub-optimally, in order to safely handle the lack of a |
| // color ticket item. |
| const defaultOption = destination.defaultColorOption; |
| if (defaultOption) { |
| cjt.print.color = {type: defaultOption.type}; |
| if (defaultOption.hasOwnProperty('vendor_id')) { |
| cjt.print.color!.vendor_id = defaultOption.vendor_id; |
| } |
| } |
| } |
| if (this.settings.copies.available) { |
| cjt.print.copies = {copies: this.getSettingValue('copies')}; |
| } |
| if (this.settings.duplex.available) { |
| cjt.print.duplex = { |
| type: this.getCddDuplexType_(), |
| }; |
| } |
| if (this.settings.mediaSize.available) { |
| const mediaValue = this.settings.mediaSize.value; |
| cjt.print.media_size = { |
| width_microns: mediaValue.width_microns, |
| height_microns: mediaValue.height_microns, |
| is_continuous_feed: mediaValue.is_continuous_feed, |
| vendor_id: mediaValue.vendor_id, |
| }; |
| } |
| if (!this.settings.layout.available) { |
| // In this case "orientation" option is hidden from user, so user can't |
| // adjust it for page content, see Landscape.isCapabilityAvailable(). |
| // We can improve results if we set AUTO here. |
| const capability = destination.capabilities!.printer ? |
| destination.capabilities!.printer.page_orientation : |
| null; |
| if (capability && capability.option && |
| capability.option.some(option => option.type === 'AUTO')) { |
| cjt.print.page_orientation = {type: 'AUTO'}; |
| } |
| } else { |
| cjt.print.page_orientation = { |
| type: this.settings.layout.value ? 'LANDSCAPE' : 'PORTRAIT', |
| }; |
| } |
| if (this.settings.dpi.available) { |
| const dpiValue = this.settings.dpi.value; |
| cjt.print.dpi = { |
| horizontal_dpi: dpiValue.horizontal_dpi, |
| vertical_dpi: dpiValue.vertical_dpi, |
| vendor_id: dpiValue.vendor_id, |
| }; |
| } |
| if (this.settings.vendorItems.available) { |
| const items = this.settings.vendorItems.value; |
| cjt.print.vendor_ticket_item = []; |
| for (const itemId in items) { |
| if (items.hasOwnProperty(itemId)) { |
| cjt.print.vendor_ticket_item.push({id: itemId, value: items[itemId]}); |
| } |
| } |
| } |
| return JSON.stringify(cjt); |
| } |
| } |
| |
| declare global { |
| interface HTMLElementTagNameMap { |
| 'print-preview-model': PrintPreviewModelElement; |
| } |
| } |
| |
| customElements.define(PrintPreviewModelElement.is, PrintPreviewModelElement); |