|
@@ -4,7 +4,8 @@ import {
|
|
|
Mesh,
|
|
|
MeshBasicMaterial,
|
|
|
PlaneGeometry,
|
|
|
- sRGBEncoding
|
|
|
+ sRGBEncoding,
|
|
|
+ Color
|
|
|
} from 'three';
|
|
|
|
|
|
class HTMLMesh extends Mesh {
|
|
@@ -14,7 +15,7 @@ class HTMLMesh extends Mesh {
|
|
|
const texture = new HTMLTexture( dom );
|
|
|
|
|
|
const geometry = new PlaneGeometry( texture.image.width * 0.001, texture.image.height * 0.001 );
|
|
|
- const material = new MeshBasicMaterial( { map: texture, toneMapped: false } );
|
|
|
+ const material = new MeshBasicMaterial( { map: texture, toneMapped: false, transparent: true } );
|
|
|
|
|
|
super( geometry, material );
|
|
|
|
|
@@ -122,6 +123,7 @@ const canvases = new WeakMap();
|
|
|
function html2canvas( element ) {
|
|
|
|
|
|
const range = document.createRange();
|
|
|
+ const color = new Color();
|
|
|
|
|
|
function Clipper( context ) {
|
|
|
|
|
@@ -192,15 +194,30 @@ function html2canvas( element ) {
|
|
|
|
|
|
}
|
|
|
|
|
|
- context.font = style.fontSize + ' ' + style.fontFamily;
|
|
|
+ context.font = style.fontWeight + ' ' + style.fontSize + ' ' + style.fontFamily;
|
|
|
context.textBaseline = 'top';
|
|
|
context.fillStyle = style.color;
|
|
|
- context.fillText( string, x, y );
|
|
|
+ context.fillText( string, x, y + parseFloat( style.fontSize ) * 0.1 );
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
+ function buildRectPath(x, y, w, h, r) {
|
|
|
+
|
|
|
+ if ( w < 2 * r ) r = w / 2;
|
|
|
+ if ( h < 2 * r ) r = h / 2;
|
|
|
+
|
|
|
+ context.beginPath();
|
|
|
+ context.moveTo( x + r, y );
|
|
|
+ context.arcTo( x + w, y, x + w, y + h, r );
|
|
|
+ context.arcTo( x + w, y + h, x, y + h, r );
|
|
|
+ context.arcTo( x, y + h, x, y, r );
|
|
|
+ context.arcTo( x, y, x + w, y, r );
|
|
|
+ context.closePath();
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
function drawBorder( style, which, x, y, width, height ) {
|
|
|
|
|
|
const borderWidth = style[ which + 'Width' ];
|
|
@@ -210,6 +227,7 @@ function html2canvas( element ) {
|
|
|
if ( borderWidth !== '0px' && borderStyle !== 'none' && borderColor !== 'transparent' && borderColor !== 'rgba(0, 0, 0, 0)' ) {
|
|
|
|
|
|
context.strokeStyle = borderColor;
|
|
|
+ context.lineWidth = parseFloat( borderWidth );
|
|
|
context.beginPath();
|
|
|
context.moveTo( x, y );
|
|
|
context.lineTo( x + width, y + height );
|
|
@@ -249,8 +267,8 @@ function html2canvas( element ) {
|
|
|
|
|
|
context.save();
|
|
|
const dpr = window.devicePixelRatio;
|
|
|
- context.scale(1/dpr, 1/dpr);
|
|
|
- context.drawImage(element, 0, 0 );
|
|
|
+ context.scale( 1 / dpr, 1 / dpr );
|
|
|
+ context.drawImage( element, 0, 0 );
|
|
|
context.restore();
|
|
|
|
|
|
} else {
|
|
@@ -266,27 +284,164 @@ function html2canvas( element ) {
|
|
|
|
|
|
style = window.getComputedStyle( element );
|
|
|
|
|
|
+ // Get the border of the element used for fill and border
|
|
|
+
|
|
|
+ buildRectPath( x, y, width, height, parseFloat( style.borderRadius ) );
|
|
|
+
|
|
|
const backgroundColor = style.backgroundColor;
|
|
|
|
|
|
if ( backgroundColor !== 'transparent' && backgroundColor !== 'rgba(0, 0, 0, 0)' ) {
|
|
|
|
|
|
context.fillStyle = backgroundColor;
|
|
|
- context.fillRect( x, y, width, height );
|
|
|
+ context.fill();
|
|
|
|
|
|
}
|
|
|
|
|
|
- drawBorder( style, 'borderTop', x, y, width, 0 );
|
|
|
- drawBorder( style, 'borderLeft', x, y, 0, height );
|
|
|
- drawBorder( style, 'borderBottom', x, y + height, width, 0 );
|
|
|
- drawBorder( style, 'borderRight', x + width, y, 0, height );
|
|
|
+ // If all the borders match then stroke the round rectangle
|
|
|
+
|
|
|
+ const borders = [ 'borderTop', 'borderLeft', 'borderBottom', 'borderRight' ];
|
|
|
+
|
|
|
+ let match = true;
|
|
|
+ let prevBorder = null;
|
|
|
+
|
|
|
+ for ( const border of borders ) {
|
|
|
+
|
|
|
+ if ( prevBorder !== null ) {
|
|
|
+
|
|
|
+ match = ( style[ border + 'Width' ] === style[ prevBorder + 'Width' ] ) &&
|
|
|
+ ( style[ border + 'Color' ] === style[ prevBorder + 'Color' ] ) &&
|
|
|
+ ( style[ border + 'Style' ] === style[ prevBorder + 'Style' ] );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( match === false ) break;
|
|
|
+
|
|
|
+ prevBorder = border;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( match === true ) {
|
|
|
+
|
|
|
+ // They all match so stroke the rectangle from before allows for border-radius
|
|
|
+
|
|
|
+ const width = parseFloat( style.borderTopWidth );
|
|
|
+
|
|
|
+ if ( style.borderTopWidth !== '0px' && style.borderTopStyle !== 'none' && style.borderTopColor !== 'transparent' && style.borderTopColor !== 'rgba(0, 0, 0, 0)' ) {
|
|
|
+
|
|
|
+ context.strokeStyle = style.borderTopColor;
|
|
|
+ context.lineWidth = width;
|
|
|
+ context.stroke();
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ // Otherwise draw individual borders
|
|
|
+
|
|
|
+ drawBorder( style, 'borderTop', x, y, width, 0 );
|
|
|
+ drawBorder( style, 'borderLeft', x, y, 0, height );
|
|
|
+ drawBorder( style, 'borderBottom', x, y + height, width, 0 );
|
|
|
+ drawBorder( style, 'borderRight', x + width, y, 0, height );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( element instanceof HTMLInputElement ) {
|
|
|
+
|
|
|
+ let accentColor = style.accentColor;
|
|
|
+
|
|
|
+ if ( accentColor === undefined || accentColor === 'auto' ) accentColor = style.color;
|
|
|
|
|
|
- if ( element.type === 'color' || element.type === 'text' || element.type === 'number' ) {
|
|
|
+ color.set( accentColor );
|
|
|
|
|
|
- clipper.add( { x: x, y: y, width: width, height: height } );
|
|
|
+ const luminance = Math.sqrt( 0.299 * ( color.r ** 2 ) + 0.587 * ( color.g ** 2 ) + 0.114 * ( color.b ** 2 ) );
|
|
|
+ const accentTextColor = luminance < 0.5 ? 'white' : '#111111';
|
|
|
|
|
|
- drawText( style, x + parseInt( style.paddingLeft ), y + parseInt( style.paddingTop ), element.value );
|
|
|
+ if ( element.type === 'radio' ) {
|
|
|
|
|
|
- clipper.remove();
|
|
|
+ buildRectPath( x, y, width, height, height );
|
|
|
+
|
|
|
+ context.fillStyle = 'white';
|
|
|
+ context.strokeStyle = accentColor;
|
|
|
+ context.lineWidth = 1;
|
|
|
+ context.fill();
|
|
|
+ context.stroke();
|
|
|
+
|
|
|
+ if ( element.checked ) {
|
|
|
+
|
|
|
+ buildRectPath( x + 2, y + 2, width - 4, height - 4, height );
|
|
|
+
|
|
|
+ context.fillStyle = accentColor;
|
|
|
+ context.strokeStyle = accentTextColor;
|
|
|
+ context.lineWidth = 2;
|
|
|
+ context.fill();
|
|
|
+ context.stroke();
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( element.type === 'checkbox' ) {
|
|
|
+
|
|
|
+ buildRectPath( x, y, width, height, 2 );
|
|
|
+
|
|
|
+ context.fillStyle = element.checked ? accentColor : 'white';
|
|
|
+ context.strokeStyle = element.checked ? accentTextColor : accentColor;
|
|
|
+ context.lineWidth = 1;
|
|
|
+ context.stroke();
|
|
|
+ context.fill();
|
|
|
+
|
|
|
+ if ( element.checked ) {
|
|
|
+
|
|
|
+ const currentTextAlign = context.textAlign;
|
|
|
+
|
|
|
+ context.textAlign = 'center';
|
|
|
+
|
|
|
+ const properties = {
|
|
|
+ color: accentTextColor,
|
|
|
+ fontFamily: style.fontFamily,
|
|
|
+ fontSize: height + 'px',
|
|
|
+ fontWeight: 'bold'
|
|
|
+ };
|
|
|
+
|
|
|
+ drawText( properties, x + ( width / 2 ), y, '✔' );
|
|
|
+
|
|
|
+ context.textAlign = currentTextAlign;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( element.type === 'range' ) {
|
|
|
+
|
|
|
+ const [min,max,value] = ['min','max','value'].map(property => parseFloat(element[property]));
|
|
|
+ const position = ((value-min)/(max-min)) * (width - height);
|
|
|
+
|
|
|
+ buildRectPath( x, y + ( height / 4 ), width, height / 2, height / 4 );
|
|
|
+ context.fillStyle = accentTextColor;
|
|
|
+ context.strokeStyle = accentColor;
|
|
|
+ context.lineWidth = 1;
|
|
|
+ context.fill();
|
|
|
+ context.stroke();
|
|
|
+
|
|
|
+ buildRectPath( x, y + ( height / 4 ),position + ( height / 2 ), height / 2, height / 4 );
|
|
|
+ context.fillStyle = accentColor;
|
|
|
+ context.fill();
|
|
|
+
|
|
|
+ buildRectPath( x + position, y, height, height, height / 2 );
|
|
|
+ context.fillStyle = accentColor;
|
|
|
+ context.fill();
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( element.type === 'color' || element.type === 'text' || element.type === 'number' ) {
|
|
|
+
|
|
|
+ clipper.add( { x: x, y: y, width: width, height: height } );
|
|
|
+
|
|
|
+ drawText( style, x + parseInt( style.paddingLeft ), y + parseInt( style.paddingTop ), element.value );
|
|
|
+
|
|
|
+ clipper.remove();
|
|
|
+
|
|
|
+ }
|
|
|
|
|
|
}
|
|
|
|
|
@@ -367,6 +522,18 @@ function htmlevent( element, event, x, y ) {
|
|
|
|
|
|
element.dispatchEvent( new MouseEvent( event, mouseEventInit ) );
|
|
|
|
|
|
+ if ( element instanceof HTMLInputElement && element.type === 'range' && ( event === 'mousedown' || event === 'click' ) ) {
|
|
|
+
|
|
|
+ const [ min, max ] = [ 'min', 'max' ].map( property => parseFloat( element[ property ] ) );
|
|
|
+
|
|
|
+ const width = rect.width;
|
|
|
+ const offsetX = x - rect.x;
|
|
|
+ const proportion = offsetX / width;
|
|
|
+ element.value = min + ( max - min ) * proportion;
|
|
|
+ element.dispatchEvent( new InputEvent( 'input', { bubbles: true } ) );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
}
|
|
|
|
|
|
for ( let i = 0; i < element.childNodes.length; i ++ ) {
|