UNPKG

devexpress-richedit

Version:

DevExpress Rich Text Editor is an advanced word-processing tool designed for working with rich text documents.

306 lines (305 loc) 15.3 kB
import { HtmlBuilder } from '../../formats/html/export/html-builder'; import { RendererClassNames } from '../../canvas/renderer-class-names'; import { UpdateFieldsOptions } from '../../model/fields/tree-creator'; import { ControlOptions } from '../../model/options/control'; import { PrintMode } from '../../model/options/printing'; import { PaperKind } from '../../model/section/paper-kind'; import { SubDocumentIntervals } from '../../model/sub-document'; import { ViewType } from '../../view-settings/views-settings'; import { Browser } from '@devexpress/utils/lib/browser'; import { UnitConverter } from '@devexpress/utils/lib/class/unit-converter'; import { Size } from '@devexpress/utils/lib/geometry/size'; import { ListUtils } from '@devexpress/utils/lib/utils/list'; import { NumberMapUtils } from '@devexpress/utils/lib/utils/map/number'; import { pdfExport } from '../../formats/pdf/api/pdf'; import { RichEditClientCommand } from '../client-command'; import { CommandBase } from '../command-base'; import { SimpleCommandState } from '../command-states'; import { UpdateFieldCommandBase } from '../fields/update-field-command-base'; import { InformationCreator } from '../../utils/information-creator'; export class PrintDocumentOnClient extends CommandBase { _nonce; isEnabled() { return super.isEnabled() && ControlOptions.isEnabled(this.control.modelManager.richOptions.control.printing) && this.control.modelManager.model.isLoaded(); } isEnabledInReadOnlyMode() { return true; } constructor(control, _nonce) { super(control); this._nonce = _nonce; } getState() { return new SimpleCommandState(this.isEnabled()); } getMode(param) { return param !== null && param !== undefined ? param : this.control.modelManager.richOptions.printing.mode; } getclosePrintDialogWithHtmlPreview(param) { return param !== null && param !== undefined ? param : this.control.modelManager.richOptions.printing.closePrintDialogWithHtmlPreview; } executeCore(_state, options) { if (this.control.commandManager.isPrintingProcessing) return false; this.control.commandManager.isPrintingProcessing = true; let mode; let closePrintDialogWithHtmlPreview; if (typeof options.param === 'object' && options.param != null) { mode = this.getMode(options.param.mode); closePrintDialogWithHtmlPreview = this.getclosePrintDialogWithHtmlPreview(options.param.closePrintDialogWithHtmlPreview); } else { mode = this.getMode(options.param); closePrintDialogWithHtmlPreview = this.control.modelManager.richOptions.printing.closePrintDialogWithHtmlPreview; } const htmlPrinting = mode == PrintMode.ClientHtml; let printWindow; if (htmlPrinting) printWindow = window.open('', 'print', `height=${window.innerHeight},width=${window.innerWidth},tabbar=no`); let needSwitchViewType = false; if (this.control.innerClientProperties.viewsSettings.viewType == ViewType.Simple) { needSwitchViewType = true; this.control.commandManager.getCommand(RichEditClientCommand.SwitchToPrintLayoutView).execute(this.control.commandManager.isPublicApiCall); } let needToggleHiddenSymbols = false; if (this.control.innerClientProperties.showHiddenSymbols) { needToggleHiddenSymbols = true; this.control.commandManager.getCommand(RichEditClientCommand.ToggleShowWhitespace).execute(this.control.commandManager.isPublicApiCall); } InformationCreator.saveWithInformation(this.control, () => { while (!this.control.layout.isFullyFormatted) this.control.layoutFormatterManager.forceFormatPage(this.control.layout.validPageCount + 1); const subDocsInfo = NumberMapUtils.toListBy(this.modelManipulator.model.subDocuments, (sd) => new SubDocumentIntervals(sd, [sd.interval])); if (this.control.modelManager.richOptions.fields.updateFieldsBeforePrint) { UpdateFieldCommandBase.updateFields(this, subDocsInfo, () => { this.printCore(htmlPrinting, printWindow, closePrintDialogWithHtmlPreview, needSwitchViewType, needToggleHiddenSymbols); }, new UpdateFieldsOptions(false, false)); } else this.printCore(htmlPrinting, printWindow, closePrintDialogWithHtmlPreview, needSwitchViewType, needToggleHiddenSymbols); }); return true; } printCore(htmlPrinting, printWindow, closePrintDialogWithHtmlPreview, needSwitchViewType, needToggleHiddenSymbols) { if (htmlPrinting) { this.generatePrintDocument(printWindow.document); printWindow.focus(); const interval = setInterval(() => { if (printWindow.document.readyState == 'complete') { printWindow.print(); if (closePrintDialogWithHtmlPreview && !Browser.AndroidMobilePlatform) printWindow.close(); clearInterval(interval); } }, 100); } else { pdfExport(this.control, (blob, _stream) => { if (window.navigator.msSaveOrOpenBlob && !Browser.Edge) window.navigator.msSaveOrOpenBlob(blob, this.control.documentInfo.getFileNameForDownload() + ".pdf"); else this.control.pdfHelperFrame.showPrintDialog(URL.createObjectURL(blob)); }, () => { }); } if (needSwitchViewType) this.control.commandManager.getCommand(RichEditClientCommand.SwitchToSimpleView).execute(this.control.commandManager.isPublicApiCall); if (needToggleHiddenSymbols) this.control.commandManager.getCommand(RichEditClientCommand.ToggleShowWhitespace).execute(this.control.commandManager.isPublicApiCall); this.control.commandManager.isPrintingProcessing = false; } generatePrintDocument(document) { const dynamicStyleAttrName = 'data-rich-dynamic-style'; const height = this.control.layout.pages[0].height; const width = this.control.layout.pages[0].width; let fontLink = ""; let divsToLoadFonts = ""; const googleFonts = this.getGoogleFonts(); if (googleFonts.length > 0) { fontLink = this.createGoogleFontStyleLink(googleFonts); divsToLoadFonts = googleFonts.reduce((prev, curr) => { const fontStyles = [`font-family:${curr}`, 'font-weight:bold', 'font-style:italic']; const result = []; for (let i = 1; i <= fontStyles.length; i++) result.push(`<div ${dynamicStyleAttrName}="font-size:1pt;position:absolute;top:-1000px;${fontStyles.slice(0, i).join(';')}">${curr}</div>`); return prev ? [prev, ...result].join('\n') : result.join('\n'); }, null); } document.documentElement.innerHTML = `<!DOCTYPE html> <html moznomarginboxes mozdisallowselectionprint> <head> ${fontLink} </head> <body> ${divsToLoadFonts} </body> </html>`; const styleElement = document.createElement("STYLE"); if (this._nonce) styleElement.setAttribute("nonce", this._nonce); styleElement.innerHTML = `@page { margin: 0; size:${width}px ${width > height ? height + 4 : height}px; }`; document.head.appendChild(styleElement); this.generatePrintContent().forEach((child) => { document.body.appendChild(child); }); document.documentElement.querySelectorAll(`[${dynamicStyleAttrName}]`).forEach((element) => { element.style.cssText = element.getAttribute(dynamicStyleAttrName); }); const styleRules = this.getPrintDocumentStyleRules(width, height); for (let selector in styleRules) { const props = styleRules[selector]; document.querySelectorAll(selector).forEach(el => { for (let i = 0; i < props.length; i++) { const prop = props[i]; const priority = prop.length == 3 && prop[2] ? 'important' : ''; el.style.setProperty(prop[0], prop[1], priority); } }); } } getPrintDocumentStyleRules(width, height) { const result = {}; result["html, body"] = [ ['margin', '0', true], ['width', `${width}px`], ['height', `${height}px`] ]; result[".dxrePageArea, .dxreColumn, .dxreRow, .dxreBox, .dxreBoxBg, .dxreParFrame, .dxreBoxSpace, .dxreAncPic, .dxreTable, .dxreTableCellBg, .dxreTableBrd, .dxreTextBoxBg"] = [ ['position', 'absolute'] ]; result[".dxreTableRowCursor, .dxreTableColumnCursor, br"] = [ ['display', 'none'] ]; this.fillZIndexStyles(result); return result; } getGoogleFonts() { return this.control.modelManager.richOptions.fonts.fonts.reduce((res, f) => { if (f.useGoogleFonts) res.push(f.fontFamily); return res; }, []); } createGoogleFontStyleLink(fontFamilies) { const url = new URL('https://fonts.googleapis.com/css'); url.searchParams.append('family', fontFamilies.join('|')); url.searchParams.append('display', 'auto'); return `<link href="${url.toString()}" rel="stylesheet" />`; } generatePrintContent() { const layout = this.control.layout; const htmlBuilder = new HtmlBuilder(); for (let i = 0, layoutPage; layoutPage = layout.pages[i]; i++) { const pageSize = PrintPageSizesCalculator.getPrintPageSize(layoutPage); const pageWidth = pageSize.width; const pageHeight = pageSize.height; const page = this.control.viewManager.printLayoutRenderer.getPageRender(i, layoutPage); const main = page.getElementsByClassName(RendererClassNames.MAIN_PAGE_AREA_CONTAINER)[0].cloneNode(true); const fo = page.getElementsByClassName(RendererClassNames.FLOATING_OBJECTS_CONTAINER)[0].cloneNode(true); const other = page.getElementsByClassName(RendererClassNames.OTHER_PAGE_AREA_CONTAINER)[0].cloneNode(true); const shapeBg = page.getElementsByClassName(RendererClassNames.SHAPE_BG_CONTAINER)[0].cloneNode(true); if (Browser.IE) { htmlBuilder .startChild('div') .configure((el) => { el.style.cssText = `position: relative; margin:0px; display:block; width:${pageWidth}px; height:${pageHeight}px;`; }) .addElement(main) .addElement(fo) .addElement(other) .addElement(shapeBg) .endChild('div') .startChild('br') .endChild('br'); } else { htmlBuilder .startChild('svg', 'http://www.w3.org/2000/svg') .configure((el) => { el.setAttribute("width", pageWidth.toString()); el.setAttribute("height", pageHeight.toString()); }) .startChild('foreignObject', 'http://www.w3.org/2000/svg') .configure((el) => { el.setAttribute("x", "0"); el.setAttribute("y", "0"); el.setAttribute("width", pageWidth.toString()); el.setAttribute("height", pageHeight.toString()); }) .startChild('div') .configure((el) => { el.setAttribute("xmlns", "http://www.w3.org/1999/xhtml"); }) .addElement(main) .addElement(fo) .addElement(other) .addElement(shapeBg) .endChild('div') .endChild('foreignObject') .endChild('svg'); } } return htmlBuilder.childElements; } fillZIndexStyles(styleRules) { let ind = 0; const names = ['TextBoxBg', 'TblRowBg', 'TblCellBg', 'ParBg', 'BoxBg', 'BoxSpace', 'Box', 'TableBorder', 'AnchoredPicture', 'TextBox']; for (let level = 0; level <= 8; level++) { for (let i = 0, name; name = names[i]; i++) { styleRules[`.dxre${name}ZL${level}`] = [['z-index', `${ind}`]]; ind++; } } } } class PrintPageSize { paperKind; vertical; horizontal; accuracy; converterToPixels; constructor(paperKind, vertical, horizontal = vertical, accuracy = 5, converterToPixels = UnitConverter.millimetersToPixel) { this.paperKind = paperKind; this.vertical = vertical; this.horizontal = horizontal; this.accuracy = accuracy; this.converterToPixels = converterToPixels; vertical.applyConverter(converterToPixels); horizontal.applyConverter(converterToPixels); } getDifference(landscape, currSize) { const size = landscape ? this.horizontal : this.vertical; return new Size(Math.abs(size.width - currSize.width), Math.abs(size.height - currSize.height)); } isAccuracyAchieved(diff) { return diff.width < this.accuracy && diff.height < this.accuracy; } } class Diff { pageSizeInfo; diff; maxDiff; constructor(pageSizeInfo, diff) { this.pageSizeInfo = pageSizeInfo; this.diff = diff; this.maxDiff = Math.max(diff.width, diff.height); } } class PrintPageSizesCalculator { static sizesList = [ new PrintPageSize(PaperKind.A4, new Size(210, 296), new Size(297, 209)), new PrintPageSize(PaperKind.Letter, new Size(UnitConverter.millimetersToPixel(216), UnitConverter.millimetersToPixel(278) - 1), new Size(UnitConverter.millimetersToPixel(280), (215)), 7, v => v), ]; static getPrintPageSize(layoutSize) { const landscape = layoutSize.width > layoutSize.height; const list = ListUtils.reducedMap(PrintPageSizesCalculator.sizesList, (info) => { const diff = info.getDifference(landscape, layoutSize); return info.isAccuracyAchieved(diff) ? new Diff(info, diff) : null; }); if (!list.length) return new Size(layoutSize.width, layoutSize.height); const finalPageInfo = ListUtils.minByCmp(list, (a, b) => a.maxDiff - b.maxDiff).pageSizeInfo; return landscape ? finalPageInfo.horizontal : finalPageInfo.vertical; } }