1 | import uuidv4 from 'uuid/v4';
|
2 | import { batchResolveAllFontsAsync } from './Fonts.web';
|
3 | import { processAllImagesAsync } from './Images.web';
|
4 | import * as util from './Utils.web';
|
5 | async 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 | }
|
26 | export async function createSVGAsync(element, options = {}) {
|
27 | return await generateSVGAsync(element, options);
|
28 | }
|
29 | export 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 | }
|
37 | export async function createPNGAsync(element, options) {
|
38 | const canvas = await draw(element, options);
|
39 | return await canvas.toDataURL('image/png');
|
40 | }
|
41 | export async function createJPEGAsync(element, { quality, ...options }) {
|
42 | const canvas = await draw(element, options);
|
43 | return await canvas.toDataURL('image/jpeg', quality);
|
44 | }
|
45 | export async function createBlobAsync(element, { quality, ...options }) {
|
46 | const canvas = await draw(element, options);
|
47 | return await util.getBlobFromCanvasAsync(canvas, quality);
|
48 | }
|
49 | async 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 | }
|
60 | function 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 | }
|
73 | async 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 | }
|
80 | async function cloneElement(element) {
|
81 | const clonedNode = await getDeepCopyForElement(element);
|
82 | const clone = await cloneChildren(element, clonedNode);
|
83 | return await processClone(element, clone);
|
84 | }
|
85 | async 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 | }
|
98 | async function processClone(original, clone) {
|
99 | if (!(clone instanceof HTMLElement)) {
|
100 |
|
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 | }
|
120 | function 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 | }
|
132 | function 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 | }
|
137 | function formatCSSText(style) {
|
138 | const content = style.getPropertyValue('content');
|
139 | return `${style.cssText} content: ${content};`;
|
140 | }
|
141 | function formatCSSProperties(style) {
|
142 | const parsed = Array.from(style)
|
143 | .map(name => formatProperty(name, style))
|
144 | .join('; ');
|
145 | return `${parsed};`;
|
146 | }
|
147 | function formatProperty(name, style) {
|
148 | return `${name}: ${style.getPropertyValue(name)}${style.getPropertyPriority(name) ? ' !important' : ''}`;
|
149 | }
|
150 | function 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 | }
|
158 | function 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 | }
|
173 | async 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 |
|
\ | No newline at end of file |