4
4
Mesh ,
5
5
MeshBasicMaterial ,
6
6
PlaneGeometry ,
7
- sRGBEncoding
7
+ sRGBEncoding ,
8
+ Color
8
9
} from 'three' ;
9
10
10
11
class HTMLMesh extends Mesh {
@@ -14,7 +15,7 @@ class HTMLMesh extends Mesh {
14
15
const texture = new HTMLTexture ( dom ) ;
15
16
16
17
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 } ) ;
18
19
19
20
super ( geometry , material ) ;
20
21
@@ -122,6 +123,7 @@ const canvases = new WeakMap();
122
123
function html2canvas ( element ) {
123
124
124
125
const range = document . createRange ( ) ;
126
+ const color = new Color ( ) ;
125
127
126
128
function Clipper ( context ) {
127
129
@@ -192,15 +194,30 @@ function html2canvas( element ) {
192
194
193
195
}
194
196
195
- context . font = style . fontSize + ' ' + style . fontFamily ;
197
+ context . font = style . fontWeight + ' ' + style . fontSize + ' ' + style . fontFamily ;
196
198
context . textBaseline = 'top' ;
197
199
context . fillStyle = style . color ;
198
- context . fillText ( string , x , y ) ;
200
+ context . fillText ( string , x , y + parseFloat ( style . fontSize ) * 0.1 ) ;
199
201
200
202
}
201
203
202
204
}
203
205
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
+
204
221
function drawBorder ( style , which , x , y , width , height ) {
205
222
206
223
const borderWidth = style [ which + 'Width' ] ;
@@ -210,6 +227,7 @@ function html2canvas( element ) {
210
227
if ( borderWidth !== '0px' && borderStyle !== 'none' && borderColor !== 'transparent' && borderColor !== 'rgba(0, 0, 0, 0)' ) {
211
228
212
229
context . strokeStyle = borderColor ;
230
+ context . lineWidth = parseFloat ( borderWidth ) ;
213
231
context . beginPath ( ) ;
214
232
context . moveTo ( x , y ) ;
215
233
context . lineTo ( x + width , y + height ) ;
@@ -249,8 +267,8 @@ function html2canvas( element ) {
249
267
250
268
context . save ( ) ;
251
269
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 ) ;
254
272
context . restore ( ) ;
255
273
256
274
} else {
@@ -266,27 +284,164 @@ function html2canvas( element ) {
266
284
267
285
style = window . getComputedStyle ( element ) ;
268
286
287
+ // Get the border of the element used for fill and border
288
+
289
+ buildRectPath ( x , y , width , height , parseFloat ( style . borderRadius ) ) ;
290
+
269
291
const backgroundColor = style . backgroundColor ;
270
292
271
293
if ( backgroundColor !== 'transparent' && backgroundColor !== 'rgba(0, 0, 0, 0)' ) {
272
294
273
295
context . fillStyle = backgroundColor ;
274
- context . fillRect ( x , y , width , height ) ;
296
+ context . fill ( ) ;
275
297
276
298
}
277
299
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 ;
282
353
283
- if ( element . type === ' color' || element . type === 'text' || element . type === 'number' ) {
354
+ color . set ( accentColor ) ;
284
355
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' ;
286
358
287
- drawText ( style , x + parseInt ( style . paddingLeft ) , y + parseInt ( style . paddingTop ) , element . value ) ;
359
+ if ( element . type === 'radio' ) {
288
360
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
+ }
290
445
291
446
}
292
447
@@ -367,6 +522,18 @@ function htmlevent( element, event, x, y ) {
367
522
368
523
element . dispatchEvent ( new MouseEvent ( event , mouseEventInit ) ) ;
369
524
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
+
370
537
}
371
538
372
539
for ( let i = 0 ; i < element . childNodes . length ; i ++ ) {
0 commit comments