44 Mesh ,
55 MeshBasicMaterial ,
66 PlaneGeometry ,
7- sRGBEncoding
7+ sRGBEncoding ,
8+ Color
89} from 'three' ;
910
1011class HTMLMesh extends Mesh {
@@ -14,7 +15,7 @@ class HTMLMesh extends Mesh {
1415 const texture = new HTMLTexture ( dom ) ;
1516
1617 const geometry = new PlaneGeometry ( texture . image . width * 0.001 , texture . image . height * 0.001 ) ;
17- const material = new MeshBasicMaterial ( { map : texture , toneMapped : false } ) ;
18+ const material = new MeshBasicMaterial ( { map : texture , toneMapped : false , transparent : true } ) ;
1819
1920 super ( geometry , material ) ;
2021
@@ -122,6 +123,7 @@ const canvases = new WeakMap();
122123function html2canvas ( element ) {
123124
124125 const range = document . createRange ( ) ;
126+ const color = new Color ( ) ;
125127
126128 function Clipper ( context ) {
127129
@@ -192,15 +194,30 @@ function html2canvas( element ) {
192194
193195 }
194196
195- context . font = style . fontSize + ' ' + style . fontFamily ;
197+ context . font = style . fontWeight + ' ' + style . fontSize + ' ' + style . fontFamily ;
196198 context . textBaseline = 'top' ;
197199 context . fillStyle = style . color ;
198- context . fillText ( string , x , y ) ;
200+ context . fillText ( string , x , y + parseFloat ( style . fontSize ) * 0.1 ) ;
199201
200202 }
201203
202204 }
203205
206+ function buildRectPath ( x , y , w , h , r ) {
207+
208+ if ( w < 2 * r ) r = w / 2 ;
209+ if ( h < 2 * r ) r = h / 2 ;
210+
211+ context . beginPath ( ) ;
212+ context . moveTo ( x + r , y ) ;
213+ context . arcTo ( x + w , y , x + w , y + h , r ) ;
214+ context . arcTo ( x + w , y + h , x , y + h , r ) ;
215+ context . arcTo ( x , y + h , x , y , r ) ;
216+ context . arcTo ( x , y , x + w , y , r ) ;
217+ context . closePath ( ) ;
218+
219+ }
220+
204221 function drawBorder ( style , which , x , y , width , height ) {
205222
206223 const borderWidth = style [ which + 'Width' ] ;
@@ -210,6 +227,7 @@ function html2canvas( element ) {
210227 if ( borderWidth !== '0px' && borderStyle !== 'none' && borderColor !== 'transparent' && borderColor !== 'rgba(0, 0, 0, 0)' ) {
211228
212229 context . strokeStyle = borderColor ;
230+ context . lineWidth = parseFloat ( borderWidth ) ;
213231 context . beginPath ( ) ;
214232 context . moveTo ( x , y ) ;
215233 context . lineTo ( x + width , y + height ) ;
@@ -249,8 +267,8 @@ function html2canvas( element ) {
249267
250268 context . save ( ) ;
251269 const dpr = window . devicePixelRatio ;
252- context . scale ( 1 / dpr , 1 / dpr ) ;
253- context . drawImage ( element , 0 , 0 ) ;
270+ context . scale ( 1 / dpr , 1 / dpr ) ;
271+ context . drawImage ( element , 0 , 0 ) ;
254272 context . restore ( ) ;
255273
256274 } else {
@@ -266,27 +284,164 @@ function html2canvas( element ) {
266284
267285 style = window . getComputedStyle ( element ) ;
268286
287+ // Get the border of the element used for fill and border
288+
289+ buildRectPath ( x , y , width , height , parseFloat ( style . borderRadius ) ) ;
290+
269291 const backgroundColor = style . backgroundColor ;
270292
271293 if ( backgroundColor !== 'transparent' && backgroundColor !== 'rgba(0, 0, 0, 0)' ) {
272294
273295 context . fillStyle = backgroundColor ;
274- context . fillRect ( x , y , width , height ) ;
296+ context . fill ( ) ;
275297
276298 }
277299
278- drawBorder ( style , 'borderTop' , x , y , width , 0 ) ;
279- drawBorder ( style , 'borderLeft' , x , y , 0 , height ) ;
280- drawBorder ( style , 'borderBottom' , x , y + height , width , 0 ) ;
281- drawBorder ( style , 'borderRight' , x + width , y , 0 , height ) ;
300+ // If all the borders match then stroke the round rectangle
301+
302+ const borders = [ 'borderTop' , 'borderLeft' , 'borderBottom' , 'borderRight' ] ;
303+
304+ let match = true ;
305+ let prevBorder = null ;
306+
307+ for ( const border of borders ) {
308+
309+ if ( prevBorder !== null ) {
310+
311+ match = ( style [ border + 'Width' ] === style [ prevBorder + 'Width' ] ) &&
312+ ( style [ border + 'Color' ] === style [ prevBorder + 'Color' ] ) &&
313+ ( style [ border + 'Style' ] === style [ prevBorder + 'Style' ] ) ;
314+
315+ }
316+
317+ if ( match === false ) break ;
318+
319+ prevBorder = border ;
320+
321+ }
322+
323+ if ( match === true ) {
324+
325+ // They all match so stroke the rectangle from before allows for border-radius
326+
327+ const width = parseFloat ( style . borderTopWidth ) ;
328+
329+ if ( style . borderTopWidth !== '0px' && style . borderTopStyle !== 'none' && style . borderTopColor !== 'transparent' && style . borderTopColor !== 'rgba(0, 0, 0, 0)' ) {
330+
331+ context . strokeStyle = style . borderTopColor ;
332+ context . lineWidth = width ;
333+ context . stroke ( ) ;
334+
335+ }
336+
337+ } else {
338+
339+ // Otherwise draw individual borders
340+
341+ drawBorder ( style , 'borderTop' , x , y , width , 0 ) ;
342+ drawBorder ( style , 'borderLeft' , x , y , 0 , height ) ;
343+ drawBorder ( style , 'borderBottom' , x , y + height , width , 0 ) ;
344+ drawBorder ( style , 'borderRight' , x + width , y , 0 , height ) ;
345+
346+ }
347+
348+ if ( element instanceof HTMLInputElement ) {
349+
350+ let accentColor = style . accentColor ;
351+
352+ if ( accentColor === undefined || accentColor === 'auto' ) accentColor = style . color ;
282353
283- if ( element . type === ' color' || element . type === 'text' || element . type === 'number' ) {
354+ color . set ( accentColor ) ;
284355
285- clipper . add ( { x : x , y : y , width : width , height : height } ) ;
356+ const luminance = Math . sqrt ( 0.299 * ( color . r ** 2 ) + 0.587 * ( color . g ** 2 ) + 0.114 * ( color . b ** 2 ) ) ;
357+ const accentTextColor = luminance < 0.5 ? 'white' : '#111111' ;
286358
287- drawText ( style , x + parseInt ( style . paddingLeft ) , y + parseInt ( style . paddingTop ) , element . value ) ;
359+ if ( element . type === 'radio' ) {
288360
289- clipper . remove ( ) ;
361+ buildRectPath ( x , y , width , height , height ) ;
362+
363+ context . fillStyle = 'white' ;
364+ context . strokeStyle = accentColor ;
365+ context . lineWidth = 1 ;
366+ context . fill ( ) ;
367+ context . stroke ( ) ;
368+
369+ if ( element . checked ) {
370+
371+ buildRectPath ( x + 2 , y + 2 , width - 4 , height - 4 , height ) ;
372+
373+ context . fillStyle = accentColor ;
374+ context . strokeStyle = accentTextColor ;
375+ context . lineWidth = 2 ;
376+ context . fill ( ) ;
377+ context . stroke ( ) ;
378+
379+ }
380+
381+ }
382+
383+ if ( element . type === 'checkbox' ) {
384+
385+ buildRectPath ( x , y , width , height , 2 ) ;
386+
387+ context . fillStyle = element . checked ? accentColor : 'white' ;
388+ context . strokeStyle = element . checked ? accentTextColor : accentColor ;
389+ context . lineWidth = 1 ;
390+ context . stroke ( ) ;
391+ context . fill ( ) ;
392+
393+ if ( element . checked ) {
394+
395+ const currentTextAlign = context . textAlign ;
396+
397+ context . textAlign = 'center' ;
398+
399+ const properties = {
400+ color : accentTextColor ,
401+ fontFamily : style . fontFamily ,
402+ fontSize : height + 'px' ,
403+ fontWeight : 'bold'
404+ } ;
405+
406+ drawText ( properties , x + ( width / 2 ) , y , '✔' ) ;
407+
408+ context . textAlign = currentTextAlign ;
409+
410+ }
411+
412+ }
413+
414+ if ( element . type === 'range' ) {
415+
416+ const [ min , max , value ] = [ 'min' , 'max' , 'value' ] . map ( property => parseFloat ( element [ property ] ) ) ;
417+ const position = ( ( value - min ) / ( max - min ) ) * ( width - height ) ;
418+
419+ buildRectPath ( x , y + ( height / 4 ) , width , height / 2 , height / 4 ) ;
420+ context . fillStyle = accentTextColor ;
421+ context . strokeStyle = accentColor ;
422+ context . lineWidth = 1 ;
423+ context . fill ( ) ;
424+ context . stroke ( ) ;
425+
426+ buildRectPath ( x , y + ( height / 4 ) , position + ( height / 2 ) , height / 2 , height / 4 ) ;
427+ context . fillStyle = accentColor ;
428+ context . fill ( ) ;
429+
430+ buildRectPath ( x + position , y , height , height , height / 2 ) ;
431+ context . fillStyle = accentColor ;
432+ context . fill ( ) ;
433+
434+ }
435+
436+ if ( element . type === 'color' || element . type === 'text' || element . type === 'number' ) {
437+
438+ clipper . add ( { x : x , y : y , width : width , height : height } ) ;
439+
440+ drawText ( style , x + parseInt ( style . paddingLeft ) , y + parseInt ( style . paddingTop ) , element . value ) ;
441+
442+ clipper . remove ( ) ;
443+
444+ }
290445
291446 }
292447
@@ -367,6 +522,18 @@ function htmlevent( element, event, x, y ) {
367522
368523 element . dispatchEvent ( new MouseEvent ( event , mouseEventInit ) ) ;
369524
525+ if ( element instanceof HTMLInputElement && element . type === 'range' && ( event === 'mousedown' || event === 'click' ) ) {
526+
527+ const [ min , max ] = [ 'min' , 'max' ] . map ( property => parseFloat ( element [ property ] ) ) ;
528+
529+ const width = rect . width ;
530+ const offsetX = x - rect . x ;
531+ const proportion = offsetX / width ;
532+ element . value = min + ( max - min ) * proportion ;
533+ element . dispatchEvent ( new InputEvent ( 'input' , { bubbles : true } ) ) ;
534+
535+ }
536+
370537 }
371538
372539 for ( let i = 0 ; i < element . childNodes . length ; i ++ ) {
0 commit comments