@@ -81,3 +81,90 @@ export function nextZIndex(increment = 1) {
8181 nextIndex += increment ;
8282 return result ;
8383}
84+
85+ const elementProperties = Object . getOwnPropertyNames ( HTMLElement . prototype ) ;
86+ const objectProperties = Object . getOwnPropertyNames ( Object . prototype ) ;
87+ function cloneNode ( node ) {
88+ const result = node . cloneNode ( false ) ;
89+ let style ;
90+ try {
91+ style = window . getComputedStyle ( node ) ;
92+ } catch {
93+ return result ;
94+ }
95+ for ( const s of style ) {
96+ result . style [ s ] = style . getPropertyValue ( s ) ;
97+ }
98+ for ( const prop of Object . getOwnPropertyNames ( node . __proto__ ) ) {
99+ if ( typeof node [ prop ] == "function" ) continue ;
100+ if ( objectProperties . includes ( prop ) ) continue ;
101+ if ( elementProperties . includes ( prop ) ) continue ;
102+ const attribute = node [ prop ] ;
103+ if ( attribute == null || attribute == "" ) continue ;
104+ result . setAttribute ( prop , attribute ) ;
105+ }
106+ for ( const child of node . childNodes ) {
107+ result . appendChild ( cloneNode ( child ) ) ;
108+ }
109+ return result ;
110+ }
111+
112+ export async function domToImage ( node ) {
113+ if ( node == null ) return undefined ;
114+
115+ const { width, height } = node . getBoundingClientRect ( ) ;
116+ const padding = 10 ;
117+ const svg = document . createElementNS ( "http://www.w3.org/2000/svg" , "svg" ) ;
118+ svg . setAttribute ( "width" , width + padding * 2 ) ;
119+ svg . setAttribute ( "height" , width + padding * 2 ) ;
120+ Array . from (
121+ document . querySelectorAll ( "link[rel='stylesheet'], style" ) ,
122+ ) . forEach ( ( tag ) => {
123+ svg . append ( cloneNode ( tag ) ) ;
124+ } ) ;
125+ const foreignObject = Object . assign (
126+ document . createElementNS ( "http://www.w3.org/2000/svg" , "foreignObject" ) ,
127+ { style : "width: 100%; height: 100%;" } ,
128+ ) ;
129+ const root = Object . assign (
130+ document . createElementNS ( "http://www.w3.org/1999/xhtml" , "div" ) ,
131+ { style : `width: 100%; height: 100%; padding: ${ ( padding * 3 ) / 4 } px;` } ,
132+ ) ;
133+ root . appendChild ( cloneNode ( node ) ) ;
134+ foreignObject . append ( root ) ;
135+ svg . appendChild ( foreignObject ) ;
136+
137+ const serializer = new XMLSerializer ( ) ;
138+ const blob = new Blob ( [ serializer . serializeToString ( svg ) ] , {
139+ type : "image/svg+xml" ,
140+ } ) ;
141+ const dataUrl = await new Promise ( ( resolve , reject ) => {
142+ const reader = new FileReader ( ) ;
143+ reader . addEventListener ( "load" , ( e ) => {
144+ resolve ( e . target . result ) ;
145+ } ) ;
146+ reader . addEventListener ( "error" , ( e ) => {
147+ reject ( e ) ;
148+ } ) ;
149+ reader . readAsDataURL ( blob ) ;
150+ } ) ;
151+
152+ const canvas = document . createElement ( "canvas" ) ;
153+ canvas . width = width + padding * 2 ;
154+ canvas . height = height + padding * 2 ;
155+ const context = canvas . getContext ( "2d" ) ;
156+ context . fillStyle = "white" ;
157+ context . fillRect ( 0 , 0 , canvas . width , canvas . height ) ;
158+ await new Promise ( ( resolve , reject ) => {
159+ const image = new Image ( ) ;
160+ image . addEventListener ( "load" , ( ) => {
161+ context . drawImage ( image , 0 , 0 ) ;
162+ resolve ( ) ;
163+ } ) ;
164+ image . addEventListener ( "error" , ( e ) => {
165+ reject ( e ) ;
166+ } ) ;
167+ image . src = dataUrl ;
168+ } ) ;
169+ return canvas . toDataURL ( "image/png" ) ;
170+ }
0 commit comments