devexpress-diagram
Version:
DevExpress Diagram Control
187 lines (176 loc) • 9.5 kB
text/typescript
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, instanceId: string) {
super(1, new ExportDOMManipulator(measurer), instanceId);
}
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(this.instanceId, 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) { }
}