UNPKG

6.89 kBJavaScriptView Raw
1import uuidv4 from 'uuid/v4';
2import { batchResolveAllFontsAsync } from './Fonts.web';
3import { processAllImagesAsync } from './Images.web';
4import * as util from './Utils.web';
5async function generateSVGAsync(element, { width, height, bgcolor, style } = {}) {
6 const clone = await cloneElement(element);
7 if (clone === undefined) {
8 throw new Error('Cannot clone null element');
9 }
10 await Promise.all([batchResolveAllFontsAsync(clone), processAllImagesAsync(clone)]);
11 if (bgcolor) {
12 clone.style.backgroundColor = bgcolor;
13 }
14 if (width) {
15 clone.style.width = `${width}px`;
16 }
17 if (height) {
18 clone.style.height = `${height}px`;
19 }
20 if (style) {
21 Object.assign(clone.style, style);
22 }
23 const svgDataUri = await makeSVGDataURIAsync(clone, width || util.getWidthForElement(element), height || util.getHeightForElement(element));
24 return svgDataUri;
25}
26export async function createSVGAsync(element, options = {}) {
27 return await generateSVGAsync(element, options);
28}
29export async function createPixelDataAsync(element, options) {
30 const canvas = await draw(element, options);
31 const context = canvas.getContext('2d');
32 if (!context) {
33 throw new Error('Canvas context is not supported.');
34 }
35 return context.getImageData(0, 0, util.getWidthForElement(element), util.getHeightForElement(element)).data;
36}
37export async function createPNGAsync(element, options) {
38 const canvas = await draw(element, options);
39 return await canvas.toDataURL('image/png');
40}
41export async function createJPEGAsync(element, { quality, ...options }) {
42 const canvas = await draw(element, options);
43 return await canvas.toDataURL('image/jpeg', quality);
44}
45export async function createBlobAsync(element, { quality, ...options }) {
46 const canvas = await draw(element, options);
47 return await util.getBlobFromCanvasAsync(canvas, quality);
48}
49async function draw(element, options) {
50 const fromSVG = await generateSVGAsync(element, options);
51 const image = await util.getImageElementFromURIAsync(fromSVG);
52 const canvas = newCanvas(element, options);
53 const context = canvas.getContext('2d');
54 if (!context) {
55 throw new Error('Canvas context is not supported.');
56 }
57 context.drawImage(image, 0, 0);
58 return canvas;
59}
60function newCanvas(element, options) {
61 const canvas = document.createElement('canvas');
62 canvas.width = options.width || util.getWidthForElement(element);
63 canvas.height = options.height || util.getHeightForElement(element);
64 if (options.bgcolor) {
65 const ctx = canvas.getContext('2d');
66 if (ctx) {
67 ctx.fillStyle = options.bgcolor;
68 ctx.fillRect(0, 0, canvas.width, canvas.height);
69 }
70 }
71 return canvas;
72}
73async function getDeepCopyForElement(element) {
74 if (element instanceof HTMLCanvasElement) {
75 const dataURL = element.toDataURL();
76 return util.getImageElementFromURIAsync(dataURL);
77 }
78 return element.cloneNode(false);
79}
80async function cloneElement(element) {
81 const clonedNode = await getDeepCopyForElement(element);
82 const clone = await cloneChildren(element, clonedNode);
83 return await processClone(element, clone);
84}
85async function cloneChildren({ childNodes }, clone) {
86 const children = Array.from(childNodes);
87 if (children.length === 0) {
88 return clone;
89 }
90 for (const child of children) {
91 const childClone = await cloneElement(child);
92 if (childClone) {
93 clone.appendChild(childClone);
94 }
95 }
96 return clone;
97}
98async function processClone(original, clone) {
99 if (!(clone instanceof HTMLElement)) {
100 // TODO: Bacon: Avoid or throw error
101 return clone;
102 }
103 const source = window.getComputedStyle(original);
104 const target = clone.style;
105 if (source.cssText) {
106 target.cssText = source.cssText;
107 }
108 else {
109 for (const prop in source) {
110 const name = source[prop];
111 target.setProperty(name, source.getPropertyValue(name), source.getPropertyPriority(name));
112 }
113 }
114 clonePseudoElement(':before', original, clone);
115 clonePseudoElement(':after', original, clone);
116 mutateInputElement(original, clone);
117 mutateSVGElementClone(clone);
118 return clone;
119}
120function clonePseudoElement(element, original, clone) {
121 const style = window.getComputedStyle(original, element);
122 const content = style.getPropertyValue('content');
123 if (content === '' || content === 'none') {
124 return;
125 }
126 const className = uuidv4();
127 clone.className = `${clone.className} ${className}`;
128 const styleElement = document.createElement('style');
129 styleElement.appendChild(formatPseudoElementStyle(className, element, style));
130 clone.appendChild(styleElement);
131}
132function formatPseudoElementStyle(className, element, style) {
133 const selector = `.${className}:${element}`;
134 const cssText = style.cssText ? formatCSSText(style) : formatCSSProperties(style);
135 return document.createTextNode(`${selector}{${cssText}}`);
136}
137function formatCSSText(style) {
138 const content = style.getPropertyValue('content');
139 return `${style.cssText} content: ${content};`;
140}
141function formatCSSProperties(style) {
142 const parsed = Array.from(style)
143 .map(name => formatProperty(name, style))
144 .join('; ');
145 return `${parsed};`;
146}
147function formatProperty(name, style) {
148 return `${name}: ${style.getPropertyValue(name)}${style.getPropertyPriority(name) ? ' !important' : ''}`;
149}
150function mutateInputElement(element, clone) {
151 if (element instanceof HTMLTextAreaElement) {
152 clone.innerHTML = element.value;
153 }
154 if (element instanceof HTMLInputElement) {
155 clone.setAttribute('value', element.value);
156 }
157}
158function mutateSVGElementClone(element) {
159 if (!(element instanceof SVGElement)) {
160 return;
161 }
162 element.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
163 if (element instanceof SVGRectElement) {
164 for (const attribute of ['width', 'height']) {
165 const value = element.getAttribute(attribute);
166 if (!value) {
167 continue;
168 }
169 element.style.setProperty(attribute, value);
170 }
171 }
172}
173async function makeSVGDataURIAsync(element, width, height) {
174 element.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
175 const serializedNode = new XMLSerializer().serializeToString(element);
176 const xhtml = util.getEscapedXHTMLString(serializedNode);
177 const foreignObject = `<foreignObject x="0" y="0" width="100%" height="100%">${xhtml}</foreignObject>`;
178 const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">${foreignObject}</svg>`;
179 return `data:image/svg+xml;charset=utf-8,${svg}`;
180}
181//# sourceMappingURL=Creator.web.js.map
\No newline at end of file