sábado, noviembre 17, 2012

Applet para la impresión silenciosa en el Web Browser del cliente

Necesitaba hacer un programa que haga la impresión silenciosa (sin interacción del usuario) de un reporte en la PC del cliente desde una aplicación Web. El reporte fue generado con JasperReports y exportado a PDF, sin embargo, después de semanas de buscar en Google no encontré una solución que cumpla con lo que necesitaba. Así que reuniendo información y ejemplos de diferentes lugares pude desarrollar esta aplicación que la dejo aquí por si alguien más la necesita, así se pueden ahorrar las semanas de tiempo que invertí.

Por motivos de seguridad java no permite que una aplicación Web tenga acceso a los recursos locales (impresión, archivos, etc.) para esto se requiere de un plugin, o como en nuestro caso de un applet.

Para comenzar, nuestro reporte fue generado usando JasperReports 4.5.1 y exportado a PDF a una carpeta local de nuestro servidor. Nuestra aplicación Web, usa Tomcat y hemos creado un servlet que va a permitir hacer la descarga del PDF, por ultimo un HTML ejecuta el applet y permite hacer la impresión sin la intervención del usuario.

Este es nuestro Servlet que va a enviar el PDF al cliente.

package pe.com.example.servlet;

import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class PrintReportServlet extends HttpServlet {

private static final long serialVersionUID = 1L;
public PrintReportServlet(){
super();
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGetAndDoPost(request, response);
}

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGetAndDoPost(request, response);
}

private void doGetAndDoPost(HttpServletRequest request, HttpServletResponse response){

File file = null;

try{
file = new File("C:\temp\archivo.pdf");

if(!file.exists())
return;

ServletContext context = getServletConfig().getServletContext();
String mimetype = context.getMimeType("C:\temp\archivo.pdf");
response.setContentType( (mimetype != null) ? mimetype : "application/octet-stream" );
response.setContentLength( (int)file.length() );
//response.setHeader( "Content-Disposition", "attachment; filename=\"" + output + "\"" );

OutputStream op = response.getOutputStream();

int length = 0;
byte[] bbuf = new byte[1024];
DataInputStream in = new DataInputStream(new FileInputStream(file));

while ((in != null) && ((length = in.read(bbuf)) != -1)){
op.write(bbuf,0,length);
}

in.close();
op.flush();
op.close();

}catch(Exception e){
System.out.println(e.getMessage());
e.printStackTrace();
}finally{
if(file != null && file.exists())
file.delete();
file = null;
}
}

}


Ahora lo que necesitamos es el código del Applet que se va a encargar de llamar al servlet y de lanzar la impresión.

Lo primero que hacemos es la lectura del archivo para esto usamos DataInputStream y ByteArrayOutputStream para hacer la lectura del archivo y guardarla en memoria. Una vez con el archivo, buscamos las impresoras que tenemos disponibles usando PrintService y PrintServiceLookup, en mi caso busco la impresora de nombre "HP Deskjet 2050 J510 series" que es donde voy a dirigir la impresión.

Una vez obtenida la impresora necesitamos un PrinterJob, que se encarga de hacer la impresión de componentes AWT. En mi caso configuro también la página en tamaño A4. Para un mayor detalle de las clases de impresión, pueden consultar aquí.

Nuestro Applet implementa la interface Printable, y se debe definir el método public int print (Graphics g, PageFormat format, int index) throws PrinterException que se encarga de hacer la impresión de los componentes AWT del applet.

Aquí es donde entra en juego las librerías de PDFRenderer-0.9.1.jar que está disponible aquí. Sucede que algunas impresoras permiten que se les envíe un documento PDF para impresión, en ese caso no necesitamos usar la clase PrinterJob ni definir el método print, bastaría con crear un objeto de tipo DocPrintJob y hacer la carga del PDF y enviarlo a impresión, como ese no es nuestro caso (la impresora no acepta PDF) debemos de cargar el PDF dentro del applet y dibujarlo dentro de los componentes del applet, eso es lo que hace PDFRenderer, se encarga de dibujar el PDF en los componentes AWT, una vez que se ha cargado y dibujado el PDF, enviamos a imprimir el componente en el cual se dibujo el PDF, en resumen, es eso lo que hacemos.

Este es el código del Applet:



package pe.com.example.applet;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.print.PageFormat;
import java.awt.print.Paper;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.ByteBuffer;
import java.security.AccessController;
import java.security.PrivilegedAction;

import javax.print.DocFlavor;
import javax.print.PrintService;
import javax.print.PrintServiceLookup;
import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.PrintRequestAttributeSet;

import com.sun.pdfview.PDFFile;
import com.sun.pdfview.PDFPage;
import com.sun.pdfview.PDFRenderer;

/**
* Applet para la impresion de archivos en formato PDF en modo silencionso en
* la PC del cliente
* Requiere de las librerias de PDFRenderer-0.9.1.jar que puede encontrarse en
* http://java.net/projects/pdf-renderer/downloads
*
* Los archivos fueron generados con JasperReports y exportados a PDF. Imprimir
* desde el HTML generaba algunos errores de formato o posición de los elementos
* sobre todo al imprimir sobre formatos pre-impresos (facturas, recibos, etc.)
* por esa razón se eligió exportarlos a PDF e imprimirlos desde ahí.
*
* Se requiere de un servlet que envié el archivo PDF al cliente, este lo recibe
* como un array de Bytes y lo envía a Imprimir en la impresora definida.
*
* Basado en los siguientes ejemplos
* http://www.javaworld.com/javaworld/jw-06-2008/jw-06-opensourcejava-pdf-renderer.html?page=1
* http://publicajava.blogspot.com/
*
* Información de la impresion en Java
* http://docs.oracle.com/javase/6/docs/technotes/guides/jps/spec/JPSTOC.fm.html
*
* @author corosco
*
*/
public class PrintApplet extends javax.swing.JApplet implements Printable {

private static final long serialVersionUID = 3325035839231751544L;

PDFFile pdfFile;

/** Initializes the applet AppletPrint */
public void init() {
try {
//URL del servlet que debe llamar para recibir el archivo PDF
this.urlService = getParameter("urlService");

java.awt.EventQueue.invokeAndWait(new Runnable() {
public void run() {
initComponents();
}
});
} catch (Exception ex) {
ex.printStackTrace();
}
}


private void initComponents() {

jPanel1 = new javax.swing.JPanel();
jBtnImprimir = new javax.swing.JButton();

setLayout(new java.awt.BorderLayout());

//Opcional, puede mostrarse una ventana con un botón para imprimir
jBtnImprimir.setText("Imprimir");
jBtnImprimir.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jBtnImprimirActionPerformed(evt);
}
});

//jPanel1.add(jBtnImprimir);

add(jPanel1, java.awt.BorderLayout.CENTER);

}

private void jBtnImprimirActionPerformed(java.awt.event.ActionEvent evt) {
comunicacionServer();//para recibir los datos del server
}


// Variables declaration - do not modify
private javax.swing.JButton jBtnImprimir;
private javax.swing.JPanel jPanel1;
// End of variables declaration

//Guardara la url del servlet al que se comunicara para imprimir
private String urlService;

private void comunicacionServer(){

//long tamFile=0;

/* Primero creamos la URL para la conexión. Tiene sentido construir la
dirección de esta forma tan "complicada" puesto que el applet solo puede
establecer conexiones con su servidor, y así, al construir la dirección
dinámicamente, no tenemos que retocar el código al irnos a otro servidor.
En todo caso, lo siguiente sería válido:
URL direccion = new URL ("http://www.javahispano.com/servlet/MiServlet");
*/
URL pagina = this.getCodeBase();
String protocolo = pagina.getProtocol();
String servidor = pagina.getHost();
int puerto = pagina.getPort();
String servlet = "";

if ( this.urlService != null && !this.urlService.equals(""))
servlet = this.urlService;
else
servlet = "/example/printReport";

URL direccion = null;
URLConnection conexion = null;
try {
direccion = new URL(protocolo, servidor, puerto, servlet);
conexion = direccion.openConnection();
System.out.println(" Url del Servlet: " + direccion.toString());
} catch (IOException ex) {
ex.printStackTrace();
}

/*Lo siguiente es decirle al navegador que no use su
cache para esta conexión, porque si lo hace vamos a
tener un página estatica, y para eso no nos metemos
en estos líos ;-). */
conexion.setUseCaches(false);

/* Ahora añadimos todas las cabeceras de HTTP que necesitemos, Cookies, contenido,
autorizacion, etc. con el método:
conexion.setRequestProperty ("cabecera", "valor");
Consultar la especificación de HTTP para más detalles. Por ejemplo, para decir
que preferentemente hablamos español: */
conexion.setRequestProperty("Accept-Language", "es");

try {


/* Procesamos la información de la forma adecuada, según se
trate de datos ASCII o binarios.
Obtenemos el stream de entrada para leer la informacion que nos envie el
server*/
System.out.println("Obteniendo DataInputStream");
DataInputStream dataIn = new DataInputStream(conexion.getInputStream());

// Recibo el numero de archivos que enviara el servidor
//int numArchivosRecibir = dataIn.readInt();
//Recibo un unico archivo
int numArchivosRecibir = 1;
System.out.println(" Numero de archivos para imprimir " + numArchivosRecibir);

// Recibe los archivos
/*String nameFiles[] = new String[numArchivosRecibir];
File f = null;
File files[] = new File[numArchivosRecibir];
FileOutputStream outputStream = null;*/
ByteArrayOutputStream outputStream = null;

System.out.println("lectura del archivo");
for(int k = 0; k < numArchivosRecibir; k++){

outputStream = new ByteArrayOutputStream();
byte buf[] = new byte[1024];
int len;

while ((dataIn != null) && ((len = dataIn.read(buf)) != -1)){
outputStream.write(buf,0,len);
}

/*
* Si tenemos varias impresoras configuradas se hace una busqueda
* de las impresoras
*/
DocFlavor psInFormat = DocFlavor.BYTE_ARRAY.AUTOSENSE;
PrintRequestAttributeSet aset = new HashPrintRequestAttributeSet();
PrintService[] services = PrintServiceLookup.lookupPrintServices(psInFormat, aset);

// this step is necessary because I have several printers configured
PrintService myPrinter = null;
for (int i = 0; i < services.length; i++){

String svcName = services[i].toString();
System.out.println("service found: " + svcName);
//Cambiar el nombre de la impresora
if (svcName.contains("HP Deskjet 2050 J510 series")){
myPrinter = services[i];
System.out.println("my printer found: "+svcName);
break;
}
}

ByteBuffer buff = ByteBuffer.wrap(outputStream.toByteArray());

pdfFile = new PDFFile(buff);

if (myPrinter != null) {
PrinterJob job = PrinterJob.getPrinterJob ();

//Se le asigna la impresora configurada
job.setPrintService(myPrinter);
try {

PageFormat pf = job.defaultPage();
Paper paper = new Paper();
paper.setSize(595, 842); //A4
paper.setImageableArea(72, 72, 523, 770);
pf.setPaper(paper);
job.setPrintable(this, pf);
job.print();

} catch (Exception pe) {
pe.printStackTrace();
}
} else {
System.out.println("no printer services found");
}

outputStream.close();

}

/* Y finalmente cerramos la conexión. */

dataIn.close();
dataIn = null;
outputStream = null;

} catch (PrinterException e) {
e.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
}
}

/**
* Método que será ejecutado desde JavaScript para lanzar la impresion
*
* @param urlConParametros ruta del servlet que envía el PDF
*/
@SuppressWarnings("unchecked")
public void ejecutaConJavascript (final String urlConParametros){

AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
try {
comunicacionServer(urlConParametros);
} catch(Exception e) {
e.printStackTrace();
return false;
}
return null;
}
});
}

public void comunicacionServer(String urlConParametros) {
this.urlService = urlConParametros;
this.comunicacionServer();
}

/**
* Método para realizar la impresión
* Las clases de PDFRenderer se encarga de dibujar el PDF en AWT y
* se lanza para impresión está ventana, se hace de esta forma porque
* no todas las impresoras aceptan directamente un archivo PDF para
* impresión.
*
* @param g
* @param format
* @param index
*/
public int print (Graphics g, PageFormat format, int index) throws PrinterException
{
int pagenum = index+1;
if (pagenum < 1 || pagenum > pdfFile.getNumPages ())
return NO_SUCH_PAGE;

Graphics2D g2d = (Graphics2D) g;
AffineTransform at = g2d.getTransform ();

PDFPage pdfPage = pdfFile.getPage (pagenum);

Dimension dim;
dim = pdfPage.getUnstretchedSize ((int) format.getImageableWidth (),
(int) format.getImageableHeight (),
pdfPage.getBBox ());

Rectangle bounds = new Rectangle ((int) format.getImageableX (),
(int) format.getImageableY (),
dim.width,
dim.height);

PDFRenderer rend = new PDFRenderer (pdfPage, (Graphics2D) g, bounds,
null, null);
try
{
pdfPage.waitForFinish ();
rend.run ();
}
catch (InterruptedException ie)
{
System.out.println(ie.getMessage());
}

g2d.setTransform (at);

return PAGE_EXISTS;
}


}


En nuestra aplicación debemos de crear una carpeta bajo el directorio Web, donde debemos de colocar los jar que contienen las clases del applet y de la librería de PDFRenderer, en mi caso he creado WebContent\resources\applet y dentro de esta carpeta he copiado los archivos appletPrintReport.jar y PDFRenderer-0.9.1.jar, el archivo appletPrintReport.jar contiene unicamente la clase pe.com.example.applet.PrintApplet. Los dos applets han sido firmado digitalmente, ya que de lo contrario aparece un mensaje de advertencia de seguridad en el Browser del cliente, para firmar el applet usan el siguiendo comando de java:

C:\> jarsigner appletPrintReport.jar certkey

Donde certkey es la clave que se ha generado usando el keytool de java. Como generar la clave, lo pueden consultar "aquí.

Para el caso del jar PDFRenderer-0.9.1.jar, antes de firmarlo debemos desempaquetarlo, puede ser con el WinRar, extraer el contenido y buscar el archivo MANIFEST.MF que está en la carpeta META-INF, dentro de ese archivo debemos de agregar está linea:

Trusted-Library: true

Sucede que esa librería tiene algunos recursos que no se firman digitalmente, razón por la cual cuando se carga el applet lanza una advertencia de seguridad informando que se está cargando código mixto seguro e inseguro. Ese detalle lo encontré después de Googlear aquí y aquí. Una vez que adicionan la línea vuelven a empaquetar el contenido usando el WinRar, pero en formato ZIP, cuando se ha generado el .ZIP, le cambian la extensión a .JAR y lo firman digitalmente como se indicó arriba.

Por ultimo necesitamos un HTML que cargue el applet, para eso usamos este HTML, que carga una pequeña ventana de 100x100 y después de 5 segundos de finalizada la impresión se cierra.

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<script type="text/javascript" language="JavaScript">
function ejecutoApplet() {
//aqui hay que mandarle el nombre que le dimos al applet
var Myapplet = document.applets['nomApplet'];
//alert(Myapplet);
var p = Myapplet.ejecutaConJavascript('/example/printReport');
//alert(p); //si el metodo del applet retorna algo con esto podemos ver el resultado

window.setInterval(function(){window.close()},5000);
}
</script>
<body onload="ejecutoApplet();">
<applet name="nomApplet" CODEBASE="resources/applet/"
code="pe.com.example.applet.PrintApplet"
archive="appletPrintReport.jar,PDFRenderer-0.9.1.jar"
width="10" height="10">
<param value="/example/printReport" name="urlService">
</applet>
</body>
</html>



Listo, eso sería, para poder desarrollar esa aplicación me base en estos dos ejemplos:

http://www.javaworld.com/javaworld/jw-06-2008/jw-06-opensourcejava-pdf-renderer.html?page=1
http://publicajava.blogspot.com/2009/10/imprimir-reporte-de-jasperreports-en-el.html

Subiré los fuentes y el ejemplo junto con algunas imagenes más tarde. Lo dejo ese ejemplo por si a alguien más le sirve.

Actualización: Sigue anexo el link con los archivos del proyecto: link Si están usando Java 7, puede aparecer un mensaje indicando que se está bloqueando la aplicación, de ser asi agregar la ruta de la aplicación (http://localhost:8080/PrintApplet/printApplet.html) a las excepciones de Java en el panel de Control. Pueden encontrar un detalle aquí

21 comentarios:

  1. Hola como estas?
    Me a resultado muy útil esta publicación!!
    Y tengo algunas dudas!!
    Una de ellas es saber si se le puede pasar parámetros de alguna forma al Applet, la idea es que el applet llame al servlet con el nombre de un archivo por ejemplo, esto es así porque si uso un archivo con un nombre genérico puedo usar información que ya fue modificada por otro usuario!!
    Espero que me puedas ayudar!!
    Desde ya muchas gracias!!

    ResponderBorrar
  2. Anónimo5:18 p. m.

    Hola, oye tengo un problema, al cargar mi aplicación web si veo mi applet pero al dar clic en el botón no ejecuta el código java correspondiente al botón.

    Alguien me puede ayudar. Saludos

    ResponderBorrar
  3. Hola Oscar, disculpa por no responder antes! Dentro del servlet PrintReportServlet tu puedes generar cualquier nombre del archivo, si te fijas en el applet ahi se está ejecutando el applet con un parámetro, que es la URL del servlet no se está enviando ningun nombre de archivo, ese ya está en el Servlet, ahi puedes generar el nombre que desees o puedes recuperar el nombre de una variable de sesión si lo deseas (si estas llamando al applet desde otro servlet). También en el mismo ya estoy enviando un parámetro que es la URL (urlService), ahí mismo está la respuesta a lo que buscas.

    ResponderBorrar
  4. Hola Anónimo, por seguridad en algunos navegadores se bloquea la ejecución de los applet, revisa si no tienes algún mensaje informando del bloqueo de la ejecución del applet. Si tienes Firefox te recomiendo que instales Firebug, y verifiques la consola si tienes algún mensaje de error.

    ResponderBorrar
  5. Hola soy nueva y tengo dudas me podrias ayudar, mira a lo que entiendo genero una aplicacion java dentro de esta agrego un japplet form donde colocas todo el codigo del applet, genero el jar y lo coloco en una carpeta de mi aplicacion web, mi duda es el servlet lo agrego en el proyecto web o en la aplicacion java del applet, disculpa pero eso no lo entiendo, porfavor ayuda.

    ResponderBorrar
  6. Hola de nuevo yo fijate que ya logre entender casi todo ya lo ejecuto y hago la conexion con el servlet el detalle ahora que me marca error al ejecutar la clase del applet me marca el siguiente error:

    Obteniendo DataInputStream
    Exception in thread "AWT-EventQueue-1" java.lang.NullPointerException
    at sun.net.www.ParseUtil.toURI(ParseUtil.java:261)
    at sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:772)
    at sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:703)
    ayuda porfavor, gracias

    ResponderBorrar
  7. Hola Cesar, estuve intentado hacer andar el applet, pero lo luego lo deje de lado, y ahora lo necesito, lo que me pasa que desde mi punto de vista esta funcionando todo ya que lo he probado y hace las impresiones, pero cuando lo llamo de la pagina falla!!
    Lo que hice es una aplicación web con netbeans y desde el index.jsp incrusto el código javascript que has hecho!! Te lo pongo debajo (el HTML va si los <>)

    !DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"
    html
    head
    meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"
    Title Insert title here /title
    /head
    script type="text/javascript" language="JavaScript"
    function ejecutoApplet() {
    //aqui hay que mandarle el nombre que le dimos al applet
    var Myapplet = document.applets['appletPrintReport'];
    //alert(Myapplet);
    var p = Myapplet.ejecutaConJavascript('/PrintReportServlet/PrintReportServlet?url=LiquidacionCodigos-060813.pdf');
    //alert(p); //si el metodo del applet retorna algo con esto podemos ver el resultado

    window.setInterval(function(){window.close()},5000);
    }
    /script
    body onload="ejecutoApplet();"
    applet name="appletPrintReport" CODEBASE="./"
    code="pe.com.example.applet.PrintApplet1.class"
    archive="appletPrintReport.jar,PDFRenderer-0.9.1.jar"
    width="10" height="10">
    param value="/PrintReportServlet/PrintReportServlet?url=LiquidacionCodigos-060813.pdf" name="urlService"
    /applet
    /body
    /html

    T agradecería mucho si me dieras una mano con este tema, si me mandas un mail te puedo pasar los proyectos en caso de que necesites mas información para verlo. Desde ya muchas gracias

    ResponderBorrar
  8. Hola Oscar,

    Puedes subir tus archivos a algún repositorio en línea para bajarlos o indicarme cual es tu e-mail.

    Por otro lado te sugiero que veas si en la consola de javascript te aparece algún error puede ser en el IE con F12, o en el Firefox con el plugin de Firebug.

    Me comentas si te aparece algún error de javascript.

    Voy a buscar el ejemplo que hice y lo voy a subir en este fin de semana por si te ayuda.

    ResponderBorrar
  9. Hola Cesar, te paso mi mail: oskarjavier03@gmail.com
    Ahí mandame un mail, y yo te paso el proyecto comprimido (es liviano). Voy a probar las opciones que me decís!! Muchas gracias por contestar.

    Saludos,
    Oscar

    ResponderBorrar
  10. Hola me puedes ayudar con mi appelts de impresion no entiendo porque no me sale

    ResponderBorrar
  11. Por favor alguien que me ayude en el aplet de impresion

    ResponderBorrar
  12. Anónimo8:06 p. m.

    Hola Cesar, necesito ayuda para poder conectarme de javascript con el applet, podes subir algun ejemplo. Lo valoraria mucho!!

    ResponderBorrar
  13. Hola Cesar

    Sabes no me sale el ejemplo que dejaste segui al pie de la letra pero nada me podrias dar una mano escribeme a mi mail mil gracias

    ResponderBorrar
  14. Sigue aqui el link con el ejemplo de la impresión para los que necesiten.

    ResponderBorrar
  15. Hola César. ¿Qué pasa con el método print, cuando no necesito hacer nada con PDF?. Yo voy a enviar un churro de líneas a imprimir. Estoy un poco perdida con éste método, y la verdad que no necesito tanto "lío"

    ResponderBorrar
  16. Hola Deba,

    El PDF contiene todo lo que se va a imprimir.

    Todo lo que necesites imprimir lo generas en el PDF y ese PDF es el que se envía a imprimir, de esa forma es como mejor se mantiene el formato, ya que de otra forma se pierde el formato al momento de imprimir.

    ResponderBorrar
  17. Hola Cesar, tube inconveniente al realizar el aplet nose si podrias hecherme una mano

    ResponderBorrar
    Respuestas
    1. Hola, dejé un link más arriba con el ejemplo, ¿probaste con ese ejemplo? ¿Cuál es el mensaje de error que tienes?

      Borrar
  18. Hola, dejé un link más arriba con el ejemplo, ¿probaste con ese ejemplo? ¿Cuál es el mensaje de error que tienes?

    ResponderBorrar
  19. Baje el ejemplo y no me funciona, voy a la ruta testPrint.html le doy imprimir y me abre la ventana emergente con el applet pero no imprime nada. Que podrá ser?

    ResponderBorrar
  20. Anónimo1:04 p. m.

    Excelente aporte!

    ResponderBorrar