UNPKG

devexpress-diagram

Version:

DevExpress Diagram Control

187 lines (176 loc) 9.45 kB
import { IModelChangesListener } from "../Model/ModelManipulator"; import { ItemChange } from "../Model/ModelChange"; import { Size } from "@devexpress/utils/lib/geometry/size"; import { UnitConverter } from "@devexpress/utils/lib/class/unit-converter"; import { Browser } from "@devexpress/utils/lib/browser"; import { RenderUtils } from "./Utils"; import { CanvasManagerBase } from "./CanvasManagerBase"; import { ITextMeasurer } from "./Measurer/ITextMeasurer"; import { svgNS, RenderHelper } from "./RenderHelper"; import { ExportDOMManipulator } from "./DOMManipulator"; import { ColorUtils } from "@devexpress/utils/lib/utils/color"; const EXPORT_IMAGE_QUALITY = 1; declare let canvg: any; export class CanvasExportManager extends CanvasManagerBase implements IModelChangesListener { static base64Start = "data:image/svg+xml;base64,"; static exportStyleRules = [ ".dxdi-canvas .shape ", ".dxdi-canvas .connector ", ".dxdi-canvas text", ".dxdi-canvas.export" ]; static exportStyleAttributes = [ "fill", "stroke", "stroke-width", "stroke-linejoin", "font-family", "font-size", "font-weight", "font-style", "text-decoration", "text-anchor" ]; constructor(private itemsContainer: SVGElement, measurer: ITextMeasurer) { super(1, new ExportDOMManipulator(measurer)); } getSvgImage(modelSize: Size, pageColor: number, exportAsInline: boolean, exportForBinaryImage: boolean): SVGSVGElement { const svgEl = RenderHelper.createSvgElement(undefined, true); const modelAbsSize = modelSize.clone().applyConverter(UnitConverter.twipsToPixelsF).clone().applyConverter(Math.ceil); RenderUtils.updateSvgElementSize(svgEl, modelAbsSize.width, modelAbsSize.height, true); svgEl.style.backgroundColor = ColorUtils.colorToHash(pageColor); this.createTextFloodFilter(undefined, svgEl, pageColor); const exportCssRules = !exportAsInline && !Browser.IE && this.getExportCssRules(); if(exportCssRules) { const style = document.createElementNS(svgNS, "style"); style.innerHTML = exportCssRules; svgEl.appendChild(style); } if(exportForBinaryImage) { const bk = document.createElementNS(svgNS, "rect"); bk.setAttributeNS(null, "x", "0"); bk.setAttributeNS(null, "y", "0"); bk.setAttributeNS(null, "height", modelAbsSize.height.toString()); bk.setAttributeNS(null, "width", modelAbsSize.width.toString()); bk.setAttributeNS(null, "fill", svgEl.style.backgroundColor); svgEl.appendChild(bk); } for(let i = 0; i < this.itemsContainer.childNodes.length; i++) { const node = this.itemsContainer.childNodes[i].cloneNode(true); if(!exportCssRules) this.inlineStyle(node, this.itemsContainer.childNodes[i]); svgEl.appendChild(node); } return svgEl; } getSvgImageUrl(modelSize: Size, pageColor: number, exportAsInline: boolean): string { const svgEl = this.getSvgImage(modelSize, pageColor, exportAsInline, false); return this.getSvgBase64String(svgEl); } private getSvgString(svgElement: Node) { return new XMLSerializer().serializeToString(svgElement); } private getSvgBase64String(svgElement: Node) { const xml = this.getSvgString(svgElement); return CanvasExportManager.base64Start + this.getBase64EncodeUnicode(xml); } private getBase64EncodeUnicode(s) { return btoa(encodeURIComponent(s).replace(/%([0-9A-F]{2})/g, (match, p1) => String.fromCharCode(parseInt("0x" + p1, 16)) )); } private getExportCssRules(): string { for(let i = 0; i < document.styleSheets.length; i++) { const rules = this.getRules(document.styleSheets[i]); if(rules) { let cssText = ""; for(let j = 0; j < rules.length; j++) { const rule = rules[j]; const selectorText = this.isCSSStyleRule(rule) ? rule.selectorText : null; if(selectorText && this.checkSelector(selectorText)) cssText += rule.cssText + "\n"; } if(cssText.length > 0) return "\n" + cssText; } } } private checkSelector(selectorText: string): boolean { for(let i = 0; i < CanvasExportManager.exportStyleRules.length; i++) if(selectorText.indexOf(CanvasExportManager.exportStyleRules[i]) === 0) return true; return false; } private getRules(styleSheet: StyleSheet) { try { return this.isCSSStyleSheet(styleSheet) ? styleSheet.rules || styleSheet.cssRules : null; } catch{ } } private isCSSStyleSheet(stylesheet: StyleSheet): stylesheet is CSSStyleSheet { return (<CSSStyleSheet>stylesheet).rules !== undefined; } private isCSSStyleRule(rule: CSSRule): rule is CSSStyleRule { return (<CSSStyleRule>rule).selectorText !== undefined; } private inlineStyle(destNode, srcNode) { for(let i = 0; i < destNode.childNodes.length; i++) { const child = destNode.childNodes[i]; if(!child.tagName) continue; if(child.tagName === "g") this.inlineStyle(child, srcNode.childNodes[i]); else if(child.style) { const style = window.getComputedStyle(srcNode.childNodes[i]); if(style !== undefined) for(let index = 0; index < CanvasExportManager.exportStyleAttributes.length; index++) { const styleProperty = CanvasExportManager.exportStyleAttributes[index]; child.style.setProperty(styleProperty, style.getPropertyValue(styleProperty)); } this.inlineStyle(child, srcNode.childNodes[i]); } } } exportSvgImage(modelSize: Size, pageColor: number, callback: (url: string) => void) { callback(this.getSvgImageUrl(modelSize, pageColor, true)); } private exportBinaryImage(modelSize: Size, pageColor: number, mimeType: string, callback: (url: string) => void, useCanvgForExportToImage?: boolean) { const modelAbsSize = this.getAbsoluteSize(modelSize).clone().applyConverter(Math.ceil); const canvasEl = document.createElement("canvas"); canvasEl.width = modelAbsSize.width; canvasEl.height = modelAbsSize.height; const ctx = canvasEl.getContext("2d"); ctx.fillStyle = ColorUtils.colorToHash(pageColor); ctx.fillRect(0, 0, modelAbsSize.width, modelAbsSize.height); if((useCanvgForExportToImage || Browser.IE) && typeof canvg === "object") this.exportBinaryImageCanvgAsync(modelSize, pageColor, canvasEl, ctx, mimeType).then((dataUrl) => callback(dataUrl)); else if(Browser.IE && typeof canvg === "function") this.exportBinaryImageCanvgOld(modelSize, pageColor, canvasEl, ctx, mimeType, callback); else { const imgEl = new Image(); imgEl.width = modelAbsSize.width; imgEl.height = modelAbsSize.height; imgEl.setAttribute("crossOrigin", "anonymous"); imgEl.onload = function() { ctx.drawImage(imgEl, 0, 0); callback(canvasEl.toDataURL(mimeType, EXPORT_IMAGE_QUALITY)); }; imgEl.src = this.getSvgImageUrl(modelSize, pageColor, true); } } private exportBinaryImageCanvgOld(modelSize: Size, pageColor: number, canvasEl: HTMLCanvasElement, ctx: CanvasRenderingContext2D, mimeType: string, callback: (url: string) => void) { const svgEl = this.getSvgImage(modelSize, pageColor, true, false); const svgStr = this.getSvgString(svgEl); ctx["drawSvg"]( svgStr, 0, 0, null, null, { renderCallback: function() { callback(canvasEl.toDataURL(mimeType, EXPORT_IMAGE_QUALITY)); } } ); } private async exportBinaryImageCanvgAsync(modelSize: Size, pageColor: number, canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, mimeType: string): Promise<string> { const svgEl = this.getSvgImage(modelSize, pageColor, true, true); const svgStr = this.getSvgString(svgEl); const v = canvg.Canvg.fromString(ctx, svgStr); await v.render(); return canvas.toDataURL(mimeType, EXPORT_IMAGE_QUALITY); } exportPngImage(modelSize: Size, pageColor: number, callback: (url: string) => void, useCanvgForExportToImage?: boolean) { this.exportBinaryImage(modelSize, pageColor, "image/png", callback, useCanvgForExportToImage); } exportJpgImage(modelSize: Size, pageColor: number, callback: (url: string) => void, useCanvgForExportToImage?: boolean) { this.exportBinaryImage(modelSize, pageColor, "image/jpeg", callback, useCanvgForExportToImage); } notifyModelChanged(changes: ItemChange[]) { } notifyPageColorChanged(color: number) { } notifyPageSizeChanged(pageSize: Size, pageLandscape: boolean) { } }