Implementación de epad-ink en web para recogida de firmas.
Las pruebas y testeos los he realizado con un epad-ink, modelo: VP9805.
Leeros la nota final si lo que queréis es implementar un sistema de firma baratillo en vuestra web app o proyecto.
Empezamos …
Antes de nada, tenemos que instalar los controladores en la máquina que usará el dispositivo, para eso prefiero usar un canvas puro y duro, pero bueno los clientes son como son.
Al instalar los drivers nos pedirá (si no lo tenemos) actualización e/o instalación del Microsoft framework correspondiente. Una vez instalados, tenemos que instalar las extensiones en los navegadores Chrome y Firefox (pasamos de IE).
- En Chrome, tenemos que buscar en la página de extensiones: epadlink https://chrome.google.com/webstore/category/extensions?hl=es&_feature=google

- En Firefox se tiene que instalar la extensión que nos ofrece el fabricante SigCaptureWeb.exe y activarla.

Una vez realizados estos pasos miramos el correcto funcionamiento con la url que nos proporciona el fabricante para el testeo.
https://www.esignemcee.net/SigCaptureWeb/sign_chrome_ff_sigcapturewebsdk.html
Al pulsar el botón sing tendría que aparecernos el cuadro de dialogo de la signatura, otra cosa que no me ha gustado… El tema de que lo haga con pop-ups ☹.

Probamos con los dos navegadores.
Vamos a copiar el código de la web y ponerlo en local para ver bien como lo hace.
Al analizar el código lo primero que vemos es que lleva incorporado su Jscript y los formularios para mostrarnos la información.
Primero analizamos como llama la pantalla de captura, para eso crearemos una página html limpia e iremos implementando lo que necesitemos.
Pagina con botón para firmar:
123456789101112
<!DOCTYPE html><html><head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>test firma</title> <link rel="stylesheet" href=""></head><body> <button id="firmame">Firma</button></body></html>
Pondremos el script en un documento a parte y lo llamaremos desde nuestra página.
Lo primero que vemos es que al cargar el <body> llama a la función <bodyonload=»ClearFormData()»>
Miremos en el Js esta función
12345
function ClearFormData() { document.FORM1.sigRawData.value = "Signature Raw Data: "; document.FORM1.sigImageData.value = "Signature Image Data: "; }
Lo único que hace es mostrarnos el texto en los dos formularios, nada de especial.
Vamos al meollo del asunto, al pulsar el botón sign.
1
<input id="”SignBtn”" name="”SignBtn”" type="”button”" value="”Sign”" onclick="”StartSign()”">
Se llama a la función StarSign(), que es la que nos interesa.
123456789101112131415161718192021222324252627282930
function StartSign(){ //definición del canvas //ojo tenemos 2 canvas ‘cnv’ y ‘SigImg’var canvasObj = document.getElementById('cnv');canvasObj.getContext('2d').clearRect(0, 0, canvasObj.width, canvasObj.height);document.FORM1.sigRawData.value = "Signature Raw Data: ";document.FORM1.sigImageData.value = "Signature Image Data: ";imgWidth = canvasObj.width;imgHeight = canvasObj.height; //mensaje que mandaremos con la configuraciónvar message = { "firstName": "", "lastName": "", "eMail": "", "location": "", "imageFormat": 1, "imageX": imgWidth, "imageY": imgHeight, "imageTransparency": false, "imageScaling": false, "maxUpScalePercent": 0.0, "rawDataFormat": "ENC", "minSigPoints": 25, "penThickness": 2, "penColor": "#000000" };//cuando haya un evento 'SigCaptureWeb_SignResponse' -> lanza función SignResponse()document.addEventListener('SigCaptureWeb_SignResponse', SignResponse, false);//convertimos el message a JSON y lo guardamos en messageDatavar messageData = JSON.stringify(message);//crea un elemento html con tagname 'SigCaptureWeb_ExtnDataElem' y lo guarda en elementvar element = document.createElement("SigCaptureWeb_ExtnDataElem");//le pone al elemento el messageData como atributoelement.setAttribute("SigCaptureWeb_MsgAttribute", messageData);//agrega el nuevo elemento a nuestro documentodocument.documentElement.appendChild(element);//se crea el evento 'Events' //eventos básicosvar evt = document.createEvent("Events");//da valor inicial al evento creado por createEvent()evt.initEvent("SigCaptureWeb_SignStartEvent", true, false); //lanza el eventoelement.dispatchEvent(evt); }

Vamos a probar en nuestro código solo esta parte.
El botón con la función StartSign(), acordémonos de poner el onclick=»StartSign()» a nuestro botón para que nos lance la función.
Nos arroja un mensaje de TypeError: canvasObj is null
Creamos en el body nuestro canvas ‘cnv’
12
<canvas id="cnv" name="cnv" width="500" height="100" style="background-color: #000;"></canvas>
Y le damos al botoncito 😀
Nos lanza un error: ReferenceError: SignResponse is not defined
Vemos que en la línea (del Js):
document.addEventListener(‘SigCaptureWeb_SignResponse’, SignResponse, false);
Se llama a la función SignResponse.
12345678910111213
function SignResponse(event){ //coge los atributos de 'SigCaptureWeb_msgAttri' var str = event.target.getAttribute("SigCaptureWeb_msgAttri"); //convierte el Json en un objeto var obj = JSON.parse(str); //llama a SetValues pasando el ancho y alto de la imagen que corresponden al tamaño de canvas 'cnv' //imgWidth = canvasObj.width; //imgHeight = canvasObj.height; SetValues(obj, imgWidth, imgHeight);}
Que copiaremos también a nuestro Js
Coge los parámetros de SigCaptureWeb_msgAttri los convierte a objeto y llama a la función SetValues pasando como parámetro el objeto, y el ancho y alto del canvas ‘cvs’
Copiemos la función SetValues en nuestro Js y analicémosla.
1234567891011121314151617181920212223242526272829303132333435363738394041
function SetValues(objResponse, imageWidth, imageHeight) { console.log("Estamos en SetValues()"); //objeto creado con parámetros de messageData var obj = JSON.parse(JSON.stringify(objResponse)); console.log("%o",obj); //cogemos canvas 'cnv' var ctx = document.getElementById('cnv').getContext('2d'); //si el objeto da algún error ... if (obj.errorMsg != null && obj.errorMsg!="" && obj.errorMsg!="undefined") { alert(obj.errorMsg); } //si no da error else { //el objeto tiene isSigned if (obj.isSigned) { //cogemos la info que nos pasa y la repartimos a nuestros forms //rawData //document.FORM1.sigRawData.value += obj.rawData; //no tenemos el form console.log("RawData : "); console.log(obj.rawData); //imageData //document.FORM1.sigImageData.value += obj.imageData; //no tenemos el form console.log("imageData : "); console.log(obj.imageData); //Image var img = new Image(); img.onload = function () { //la ponemos en canvas ctx.drawImage(img, 0, 0, imageWidth, imageHeight); } img.src = "data:image/png;base64," + obj.imageData; } } }
Nos quedamos con nuestro html:
1234567891011121314
<!DOCTYPE html><html><head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>test firma</title></head><body> <canvas id="cnv" name="cnv" width="500" height="100" style="background-color: #000;"></canvas> <br> <button onclick="StartSign()">Firma</button><script src="firma.js" type="text/javascript"></script></body></html>
I nuestro archivo .js
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
var imgWidth; //500var imgHeight; //100 function StartSign() { console.log("Estamos en StartSign()"); //definición del canvas var canvasObj = document.getElementById('cnv'); canvasObj.getContext('2d').clearRect(0, 0, canvasObj.width, canvasObj.height); //document.FORM1.sigRawData.value = "Signature Raw Data: "; //document.FORM1.sigImageData.value = "Signature Image Data: "; imgWidth = canvasObj.width; imgHeight = canvasObj.height; //mensaje que mandaremos con la configuración var message = { "firstName": "", "lastName": "", "eMail": "", "location": "", "imageFormat": 1, "imageX": imgWidth, "imageY": imgHeight, "imageTransparency": false, "imageScaling": false, "maxUpScalePercent": 0.0, "rawDataFormat": "ENC", "minSigPoints": 25, "penThickness": 2, "penColor": "#000000" }; //cuando haya un evento 'SigCaptureWeb_SignResponse' -> lanza función SignResponse() document.addEventListener('SigCaptureWeb_SignResponse', SignResponse, false); //convertimos el message a JSON y lo guardamos en messageData var messageData = JSON.stringify(message); //crea un elemento html con tagname 'SigCaptureWeb_ExtnDataElem' y lo guarda en element var element = document.createElement("SigCaptureWeb_ExtnDataElem"); //le pone al elemento el messageData como atributo element.setAttribute("SigCaptureWeb_MsgAttribute", messageData); //agrega el nuevo elemento a nuestro documento document.documentElement.appendChild(element); //se crea el evento 'Events' //eventos básicos var evt = document.createEvent("Events"); //da valor inicial al evento creado por createEvent() evt.initEvent("SigCaptureWeb_SignStartEvent", true, false); console.log('-lanzamos el evento'); //lanza el evento element.dispatchEvent(evt); } function SignResponse(event){ console.log("Tenemos respuesta ..."); //coge los atributos de 'SigCaptureWeb_msgAttri' var str = event.target.getAttribute("SigCaptureWeb_msgAttri"); //convierte el Json en un objeto var obj = JSON.parse(str); //llama a SetValues pasando el ancho y alto de la imagen que corresponden al tamaño de canvas 'cnv' // imgWidth = canvasObj.width; // imgHeight = canvasObj.height; SetValues(obj, imgWidth, imgHeight);} function SetValues(objResponse, imageWidth, imageHeight){ console.log("Estamos en SetValues()"); //objeto creado con parámetros de messageData var obj = JSON.parse(JSON.stringify(objResponse)); console.log("%o",obj); //cogemos canvas 'cnv' var ctx = document.getElementById('cnv').getContext('2d'); //si el objeto da algun error ... if (obj.errorMsg != null && obj.errorMsg!="" && obj.errorMsg!="undefined") { alert(obj.errorMsg); } //si no da error else { //el objeto tiene isSigned if (obj.isSigned) { //cogemos la info que nos pasa y la repartimos a nuestros forms //rawData //document.FORM1.sigRawData.value += obj.rawData; //no tenemos el form console.log("RawData : "); console.log(obj.rawData); //imageDat//document.FORM1.sigImageData.value += obj.imageData; //no tenemos el form console.log("imageData : "); console.log(obj.imageData); //Image var img = new Image(); img.onload = function () { ctx.drawImage(img, 0, 0, imageWidth, imageHeight); } img.src = "data:image/png;base64," + obj.imageData; } }}
Vamos a ordenar un poco el Js y de paso lo acortamos un poco:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
var imgWidth; var imgHeight; function StartSign() { console.log("Estamos en StartSign()"); //definición del canvas var canvasObj = document.getElementById('cnv'); canvasObj.getContext('2d').clearRect(0, 0, canvasObj.width, canvasObj.height); imgWidth = canvasObj.width; imgHeight = canvasObj.height; //mensaje que mandaremos con la configuración var message = { "firstName": "", "lastName": "", "eMail": "", "location": "", "imageFormat": 1, "imageX": imgWidth, "imageY": imgHeight, "imageTransparency": false, "imageScaling": false, "maxUpScalePercent": 0.0, "rawDataFormat": "ENC", "minSigPoints": 25, "penThickness": 2, "penColor": "#000000" }; //cuando haya un evento 'SigCaptureWeb_SignResponse' -> lanza función SignResponse() document.addEventListener('SigCaptureWeb_SignResponse', SignResponse, false); //crea un elemento html con tagname 'SigCaptureWeb_ExtnDataElem' y lo guarda en element var element = document.createElement("SigCaptureWeb_ExtnDataElem"); //le pone al elemento el messageData como atributo pasado a Json element.setAttribute("SigCaptureWeb_MsgAttribute", JSON.stringify(message) ); //agrega el nuevo elemento a nuestro documento document.documentElement.appendChild(element); //se crea el evento 'Events' //eventos básicos var evt = document.createEvent("Events"); //da valor inicial al evento creado por createEvent() evt.initEvent("SigCaptureWeb_SignStartEvent", true, false); console.log('-lanzamos el evento'); //lanza el evento element.dispatchEvent(evt); } function SignResponse(event) { console.log("Tenemos respuesta ..."); //coge los atributos de 'SigCaptureWeb_msgAttri' var str = event.target.getAttribute("SigCaptureWeb_msgAttri"); //convierte el Json en un objeto var obj = JSON.parse(str); console.log("atributo SignCaptureWeb_msgAttri :") //console.log("%o",obj); //definimos canvas var ctx = document.getElementById('cnv').getContext('2d'); //cogemos datos de respuesta obj if (obj.errorMsg != null && obj.errorMsg!="" && obj.errorMsg!="undefined") { alert(obj.errorMsg); } //si no da error else { //el objeto tiene isSigned if (obj.isSigned) { //Image var img = new Image(); img.onload = function () { ctx.drawImage(img, 0,0, imgWidth, imgHeight ); } img.src = "data:image/png;base64," + obj.imageData; //tambien tenemos //obj.rawData; } } }
Así nos vale ?
Podemos poner la imagen a nuestro html para que se vea que se ha firmado, y pasar los datos obj.imageData a un campo oculto de formulario para mandar la imagen… como texto.
Si quisiéramos guardar la imagen enviada desde php podemos usar:
123456789101112131415
$file_name =”archivo.jpg”;$output_file = $_SERVER['DOCUMENT_ROOT'].$file_name;$ruta_file = base64_to_jpeg($base64_string, $output_file); function base64_to_jpeg($base64_string, $output_file) { // abrimos archivo para escribir, si no existe lo creamos $ifp = fopen( $output_file, 'w+' ); $data = $base64_string ; //podríamos agregar validación asegurando que $data>1 fwrite( $ifp, base64_decode( $data ) ); // cerramos el archivo resultado fclose( $ifp ); //retornamos el nombre del archivo return $output_file; }
Vamos a testearlo todo:
Con Laragon o Xampp o el que uséis montamos una carpeta de prueba y ponemos nuestro archivo index.html
1234567891011121314151617181920212223
<!DOCTYPE html><html><head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>test firma</title></head><body> <canvas id="cnv" name="cnv" width="500" height="100" style="background-color: #000;"></canvas> <br> <button onclick="StartSign()">Firma</button> <br> Campo que estara oculto, en el que recojemos obj.imageData. <br> <form name="myForm" action="guarda_imagen.php" method="POST"> <textarea name="dataFirma" id="dataFirma" rows="10" cols="60"></textarea><br> <button onclick="mandarFirma()">Mandar firma a archivo</button></form> <script src="firma_2.js" type="text/javascript"></script></body></html>

Tenemos el canvas (en negro, de la firma), el botón para firmar, un textarea donde cogeremos el data de la firma y el botón de mandar la firma a archivo.
Primero, a la función que llama el botón firma, le añadiremos una línea para que nos borre el contenido del textarea.
12345
function StartSign() { //borramos el textarea document.getElementById('dataFirma').value=""; …
En nuestro Js crearemos una nueva función mandarFirma().
123
function mandarFirma(){ document.getElementById("myForm").submit();}
Que nos mandara el formulario.
Y finalmente nuestro archivo .php que recepcionará los datos del formulario.
Lo primero, dentro de la función (la retocada por nosotros),
SignResponse(event), en la parte donde copiamos imagen a canvas, añadimos:
123456789101112
…if (obj.isSigned) { //Image var img = new Image(); img.onload = function () { ctx.drawImage(img, 0,0, imgWidth, imgHeight ); } img.src = "data:image/png;base64," + obj.imageData; //ponemos en el campo de nuestro form la data de la imagen document.getElementById('dataFirma').value = obj.imageData; }
Que nos copiará el contenido de obj.imageData a nuestro textarea.
Ejecutando tendríamos:

La firma en nuestro canvas y el textarea con la información que vamos a mandar al .php para que nos genere el .jpg.
En el archivo guarda_imagen.php:
12345678910111213141516171819202122
<?phpif(isset($_POST['dataFirma'])){ $base64_string = $_POST['dataFirma']; $file_name ="archivo.jpg"; $output_file = $_SERVER['DOCUMENT_ROOT'].$file_name; $ruta_file = base64_to_jpeg($base64_string, $output_file);} function base64_to_jpeg($base64_string, $output_file) { // abrimos el archivo para escribir, si no existe creamos $ifp = fopen( $output_file, 'w+' ); $data = $base64_string; //podríamos agregar validación asegurando que $data>1 fwrite( $ifp, base64_decode( $data ) ); //cerramos el archivo resultado fclose( $ifp ); //mostramos la ruta con el nombre_archivo echo $output_file;} ?>
¡Y, al pulsar en firma y mandar los datos… Voila! Tenemos el archivo .jpg en nuestra ruta ?

I asta aquí todo … os dejo los codigos para que testeeis 😉
Nota del Autor :
Se tiene que decir que encuentro carísimo el tener que implementar una firma en un documento con este sistema, el aparato es caro, las pruebas las he realizado con el modelo VP9805 (253 €, una burrada!! ), además necesita de plugins para el navegador y drivers para el equipo, cosa que te ata a usar un navegador en concreto e instalar drivers de terceros en el equipo.
Mi idea original fue poner un canvas en el formulario en el que se pudiese dibujar y con una tableta grafica (de las más baratas, 20 € en amazon) poder firmar sobre el canvas.
Si os interesa en otro post lo cuento!