11import { 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