Skip to content

Commit 79ab1b9

Browse files
committed
Improve capture logic
1 parent 091484c commit 79ab1b9

1 file changed

Lines changed: 115 additions & 47 deletions

File tree

src/core/capture.js

Lines changed: 115 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,127 @@
11
import { prepareClone } from './prepare.js';
2-
import { inlineFonts } from '../modules/fonts.js';
3-
import { minifySVG } from '../utils/minifySVG.js';
2+
import { inlineImages } from '../modules/images.js';
3+
import { inlineBackgroundImages } from '../modules/background.js';
4+
import { idle } from '../utils/helpers.js';
5+
import { collectUsedTagNames, generateDedupedBaseCSS } from '../utils/cssTools.js';
6+
import { embedCustomFonts } from '../modules/fonts.js';
7+
import { baseCSSCache } from '../core/cache.js'
48

59
/**
610
* Captures an HTML element as an SVG data URL
711
* @param {Element} element - DOM element to capture
812
* @param {Object} [options={}] - Capture options
9-
* @param {number} [options.scale=1] - Scale factor for the output image
10-
* @returns {Promise<string>} Promise that resolves to SVG data URL
13+
* @returns {Promise<string>} Promise that resolves to SVG Blob
1114
*/
1215

13-
export async function capture(element, options = {}) {
14-
const { scale = 1} = options;
16+
export async function captureDOM(element, options = {}) {
17+
if (!element) throw new Error('Element cannot be null or undefined');
18+
19+
const { compress = true, embedFonts = false, fast = true, scale = 1 } = options;
1520
let clone, classCSS, styleCache;
16-
try {
17-
({ clone, classCSS, styleCache } = await prepareClone(element));
18-
} catch (e) {
19-
console.error("prepareClone failed:", e);
20-
throw e;
21+
let fontsCSS = '';
22+
let baseCSS = '';
23+
let dataBlob;
24+
let svgString;
25+
26+
// 1. Clonación + pseudo
27+
({ clone, classCSS, styleCache } = await prepareClone(element, compress));
28+
29+
// 2. Inline images (una sola vez)
30+
await new Promise(resolve => {
31+
idle(async () => {
32+
await inlineImages(clone);
33+
resolve();
34+
}, { fast });
35+
});
36+
37+
// 3. Inline background images (una sola vez)
38+
await new Promise(resolve => {
39+
idle(async () => {
40+
await inlineBackgroundImages(element, clone, styleCache);
41+
resolve();
42+
}, { fast });
43+
});
44+
45+
// 4. Embed fonts si aplica
46+
if (embedFonts) {
47+
await new Promise(resolve => {
48+
idle(async () => {
49+
fontsCSS = await embedCustomFonts({ ignoreIconFonts: true });
50+
resolve();
51+
}, { fast });
52+
});
2153
}
22-
const rect = element.getBoundingClientRect();
23-
const w = rect.width * scale;
24-
const h = rect.height * scale;
25-
let fonts = "";
26-
try {
27-
fonts = await inlineFonts(styleCache);
28-
} catch (e) {
29-
console.warn("inlineFonts failed:", e);
54+
55+
// 5. Generar baseCSS para compresión si aplica, con caché de tags
56+
if (compress) {
57+
const usedTags = collectUsedTagNames(clone).sort();
58+
const tagKey = usedTags.join(',');
59+
if (baseCSSCache.has(tagKey)) {
60+
baseCSS = baseCSSCache.get(tagKey);
61+
} else {
62+
await new Promise(resolve => {
63+
idle(() => {
64+
baseCSS = generateDedupedBaseCSS(usedTags);
65+
baseCSSCache.set(tagKey, baseCSS);
66+
resolve();
67+
}, { fast });
68+
});
69+
}
3070
}
31-
// Create SVG container
32-
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
33-
svg.setAttribute("width", w);
34-
svg.setAttribute("height", h);
35-
svg.setAttribute("viewBox", `0 0 ${w} ${h}`);
36-
// Create foreignObject to contain HTML content
37-
const fo = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject");
38-
fo.setAttribute("width", "100%");
39-
fo.setAttribute("height", "100%");
40-
// Create container for HTML content
41-
const div = document.createElement("div");
42-
div.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
43-
div.setAttribute("style", `width:${w}px;height:${h}px;`);
44-
// Add style and content to the container
45-
const styleTag = document.createElement("style");
46-
styleTag.textContent = fonts + "svg{overflow:visible}" + classCSS;
47-
div.appendChild(styleTag);
48-
div.appendChild(clone);
49-
fo.appendChild(div);
50-
svg.appendChild(fo);
51-
try {
52-
// Serialize and encode SVG
53-
const svgStr = new XMLSerializer().serializeToString(svg);
54-
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(minifySVG(svgStr))}`;
55-
} catch (e) {
56-
console.error("SVG serialization failed:", e);
57-
throw e;
71+
72+
// 6. Montaje del SVG final con serialización de sólo <foreignObject>
73+
await new Promise(resolve => {
74+
idle(() => {
75+
const rect = element.getBoundingClientRect();
76+
const w = rect.width * scale;
77+
const h = rect.height * scale;
78+
79+
// Ajuste de escala si aplica
80+
if (scale !== 1) {
81+
clone.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
82+
clone.style.transform = `scale(${scale})`;
83+
clone.style.transformOrigin = 'top left';
84+
clone.style.width = `${rect.width}px`;
85+
clone.style.height = `${rect.height}px`;
86+
}
87+
88+
// Preparar svg y foreignObject
89+
const svgNS = 'http://www.w3.org/2000/svg';
90+
const fo = document.createElementNS(svgNS, 'foreignObject');
91+
fo.setAttribute('width', '100%');
92+
fo.setAttribute('height', '100%');
93+
94+
const styleTag = document.createElement('style');
95+
styleTag.textContent = baseCSS + fontsCSS + 'svg{overflow:visible;}' + classCSS;
96+
fo.appendChild(styleTag);
97+
fo.appendChild(clone);
98+
99+
// Serializar sólo el foreignObject
100+
const serializer = new XMLSerializer();
101+
const foString = serializer.serializeToString(fo);
102+
const svgHeader = `<svg xmlns="${svgNS}" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">`;
103+
const svgFooter = '</svg>';
104+
svgString = svgHeader + foString + svgFooter;
105+
106+
// Crear blob y object URL para evitar serializar todo el DOM
107+
const blob = new Blob([svgString], { type: 'image/svg+xml;charset=utf-8' });
108+
dataBlob = URL.createObjectURL(blob);
109+
110+
resolve();
111+
}, { fast });
112+
});
113+
114+
// 7. Limpieza del sandbox si existe
115+
const sandbox = document.getElementById('snapdom-sandbox');
116+
if (sandbox && sandbox.style.position === 'absolute') sandbox.remove();
117+
118+
if (options.dataURL) {
119+
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`;
120+
121+
} else {
122+
return dataBlob;
123+
58124
}
59-
}
125+
126+
// return dataURL;
127+
}

0 commit comments

Comments
 (0)