org.viafirma.util.QRCodeUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.viafirma.util.QRCodeUtil.java

Source

/* 
 * File: QRCodeUtil.java
 *
 * Created on Aug 18, 2007
 *
 *
 * Copyright 2006-2007 Felix Garcia Borrego (borrego@gmail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package org.viafirma.util;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javax.imageio.ImageIO;

import net.sourceforge.barbecue.BarcodeFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.viafirma.excepciones.CodigoError;
import org.viafirma.excepciones.ExcepcionErrorInterno;
import org.viafirma.plugin.AuthoritySupportPlugin;
import org.viafirma.plugin.PluginLocator;
import org.viafirma.vo.CertificadoGenerico;

import com.lowagie.text.BadElementException;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Image;
import com.lowagie.text.Paragraph;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfImportedPage;
import com.lowagie.text.pdf.PdfPCell;
import com.lowagie.text.pdf.PdfPTable;
import com.lowagie.text.pdf.PdfReader;
import com.lowagie.text.pdf.PdfWriter;
import com.swetake.util.Qrcode;

/**
 * Genera etiquetas basadas en QRCode Y Pdf de justificante.
 * 
 * @author Felix Garcia Borrego (borrego at gmail.com)
 * @author Alexis Castilla Armero (pencerval at gmail.com)
 */
public class QRCodeUtil {

    private static QRCodeUtil singleton;

    private static boolean doUrlFriendly = true;

    private static String urlFriendly;

    public static QRCodeUtil getInstance() {
        if (singleton == null) {
            throw new ExceptionInInitializerError("QRCodeUtil no esta inicializado.");
        }
        return singleton;
    }

    public static void init(Properties properties) {
        if (properties.getProperty(Constantes.PRINT_URL_FRIENDLY) != null)
            doUrlFriendly = Boolean.valueOf(properties.getProperty(Constantes.PRINT_URL_FRIENDLY));
        urlFriendly = properties.getProperty(Constantes.PUBLIC_URL_FRIENDLY);
        singleton = new QRCodeUtil();
    }

    /**
     * URI a la imagen de cabecera utilizada en los pdfs.
     */
    private final static String IMAGE_CABECERA = "/cabecera-pdf.png";

    // ************************************************************
    // ** Datos para la generacin de la etiqueta QR
    /**
     * Ancho de la etiqueta que contiene el QR
     */
    private final static int ANCHO_ETIQUETA = 1100;

    /**
     * Alto de la etiqueta que contiene el QR
     */
    private final static int ALTO_ETIQUETA = 254;

    /**
     * Marjen de la caja contenedora de la etiqueta
     */
    private final static int MARGEN_CAJA = 10;

    /**
     * Marjen de todos los elementos de la etiqueta
     */
    private final static int MARGEN = 25;

    /**
     * Tamao de los caracteres.
     */
    private static final int FONT_SIZE = 18;

    /**
     * Tamao de los pixeles del QR.
     */
    private static final int SIZE_QR = 5;

    /**
     * Nmero mximo de caracteres por linea.
     */
    private static final int MAX_SIZE_LINEA = 75;

    /**
     * Nmero maximo de lineas de texto en la etiqueta.
     */
    private static final int MAX_ROWS = 7;

    /**
     * Nmero mximo de caracteres que van a ser codificados dentro del QRCODE
     */
    private static final int MAX_TEXT_SIZE = 130;

    // ************************************************************
    // ** Datos para el formateado del PDF
    /**
     * Seperacin entre la tabla de vista previa y el cdigo CR_CODE
     */
    private final static int SPACE_SEPARACION = 10;

    /**
     * Tamao del cdigo QR en el pdf.
     */
    private final static float HEIGHT_QR_CODE_PDF = 130;

    private static Log log = LogFactory.getLog(Qrcode.class);

    /**
     * Genera un nuevo pdf con la imagen firmada y el pie de firma.
     * @param url Url desde la que se puede descargar el documeto
     */
    public void generarImagenPdf(byte[] input, String texto, String url, String textoQR, String codFirma,
            OutputStream out) throws ExcepcionErrorInterno {
        try {
            // Nuevo Documento PDF
            Document document = new Document();

            // Obtenemos el tamao de la pgina
            Rectangle pageSize = document.getPageSize();

            PdfWriter.getInstance(document, out);
            document.open();
            addContent(texto, url, textoQR, codFirma, document, pageSize, true, false);

            // Aadimos imagen original
            Image imagen = Image.getInstance(input);
            imagen.setBorder(10);
            imagen.scaleToFit(500, 500);
            imagen.setAbsolutePosition(50, pageSize.getHeight() - 400);
            document.add(imagen);

            // Cerramos el documento
            document.close();

        } catch (Exception e) {
            throw new ExcepcionErrorInterno(CodigoError.ERROR_INTERNO, e);
        }

    }

    /**
     * Genera un NUEVO pdf con la imagen firmada y el pie de firma.
     */
    public void generarPdf(String texto, String texto2Linea, String textoQR, String codFirma, OutputStream out)
            throws ExcepcionErrorInterno {
        try {
            // Nuevo Documento PDF
            Document document = new Document();

            // Obtenemos el tamao de la pgina
            Rectangle pageSize = document.getPageSize();

            PdfWriter.getInstance(document, out);
            document.open();
            addContent(texto, texto2Linea, textoQR, codFirma, document, pageSize, false, false);

            // Cerramos el documento
            document.close();

        } catch (Exception e) {
            throw new ExcepcionErrorInterno(CodigoError.ERROR_INTERNO, e);
        }

    }

    /**
     * @param texto
     *            Texto a colocar
     * @param textoQR
     *            Imagen QR a colocar
     * @param codFirma
     *            Cdigo de Firma a generar.
     * @param url Url del servicio de verificacin desde donde se puede descargar el documento.
     * @param document
     *            Documeto destino
     * @param pageSize
     *            Tamao de la pgina
     * @return 
     * @throws BadElementException
     * @throws MalformedURLException
     * @throws IOException
     * @throws DocumentException
     * @throws ExcepcionErrorInterno
     */
    private static float addContent(String texto, String url, String textoQR, String codFirma, Document document,
            Rectangle pageSize, boolean completo, boolean isSellado) throws BadElementException,
            MalformedURLException, IOException, DocumentException, ExcepcionErrorInterno {

        Image imagenCabezera = null;
        float widthCabecera = 0;
        float heightCabecera = 0;
        if (!isSellado) {
            // Recuperamos la imagen de cabecera
            imagenCabezera = Image.getInstance(QRCodeUtil.class.getResource(IMAGE_CABECERA));
            // Colocamos la imagen en la zona superior de la pgina
            imagenCabezera.setAbsolutePosition(0, pageSize.getHeight() - imagenCabezera.getHeight());
            heightCabecera = imagenCabezera.getHeight();
            widthCabecera = imagenCabezera.getWidth();
        }

        // Recuperamos el pie de firma
        Image imagenQR = Image.getInstance(QRCodeUtil.getInstance().generate(texto, url, textoQR, codFirma));

        float rescalado = pageSize.getWidth() / (widthCabecera + document.leftMargin() + document.rightMargin());
        float sizeCabecera = 0;
        if (rescalado < 1f && !isSellado) {
            // Requiere rescalado, la imagen es mayor que la pgina.
            imagenCabezera.scalePercent(rescalado * 100);
            sizeCabecera = rescalado * imagenCabezera.getHeight();
        }

        float sizeCabecerayPie = HEIGHT_QR_CODE_PDF + sizeCabecera + document.bottomMargin() + document.topMargin()
                + SPACE_SEPARACION;
        // float escaleQR = HEIGHT_QR_CODE_PDF / (imagenQR.getHeight() + 5);
        float escaleQR = (pageSize.getWidth() - document.leftMargin() - document.rightMargin())
                / (imagenQR.getWidth() + 6);

        // imagen.setSpacingAfter(120);
        if (!isSellado) {
            document.add(imagenCabezera);
        }
        // Aadimos una caja de texto si estamos mostrando el contenido del
        // fichero firmado.
        if (completo) {
            // Aadimos el espacio ocupado por la imagen
            Paragraph p = new Paragraph("");
            p.setSpacingAfter(heightCabecera * rescalado);
            document.add(p);

            // Aadimos una tabla con borde donde colocar el documento.
            PdfPTable table = new PdfPTable(1);
            table.setWidthPercentage(100);
            table.getDefaultCell().setBorderWidth(10);
            PdfPCell celda = new PdfPCell();
            celda.setFixedHeight(pageSize.getHeight() - sizeCabecerayPie);
            table.addCell(celda);
            table.setSpacingAfter(SPACE_SEPARACION);
            document.add(table);
        }

        if (completo) {
            // La imagen la colocamos justo debajo
            imagenQR.setAbsolutePosition(document.leftMargin(),
                    escaleQR * HEIGHT_QR_CODE_PDF - SPACE_SEPARACION * 2);
        } else {
            imagenQR.setAbsolutePosition(document.leftMargin(), pageSize.getHeight() - sizeCabecerayPie);
        }
        imagenQR.scalePercent(escaleQR * 100);
        document.add(imagenQR);

        return sizeCabecerayPie;
    }

    /**
     * Genera un justificante de firma de un fichero pdf de entrada.
     * 
     * @param input
     *            Fichero pdf de entrada
     * @param texto
     * @param textoQR
     * @param codFirma
     * @param out
     * @throws ExcepcionErrorInterno
     */
    public void firmarPDF(InputStream input, String texto, String texto2Line, String textoQR, String codFirma,
            OutputStream out) throws ExcepcionErrorInterno {
        // leemos el pdf utilizando iText.
        try {
            // Recuperamos el documento original
            PdfReader reader = new PdfReader(input);

            // Obtenemos el tamao de la pgina
            Rectangle pageSize = reader.getPageSize(1);

            // Creamos un nuevo documento del mismo tamao que el original
            Document document = new Document(pageSize);

            // creo una instancia para escritura en el documento
            PdfWriter writer = PdfWriter.getInstance(document, out);
            document.open();

            // insertamos la portada del documento que estamos firmando.
            float escala = (pageSize.getHeight() - 350) / pageSize.getHeight();

            // Aadimos al documento la imagen de cabecera y de pie
            addContent(texto, texto2Line, textoQR, codFirma, document, pageSize, true, false);

            PdfContentByte cb = writer.getDirectContent();

            PdfImportedPage portada = writer.getImportedPage(reader, 1);
            cb.setRGBColorStroke(0xCC, 0xCC, 0xCC);
            cb.transform(AffineTransform.getTranslateInstance(document.leftMargin(), 210));
            cb.transform(AffineTransform.getScaleInstance(escala, escala));
            cb.addTemplate(portada, 0, 0);// , escala, 0, 0, escala,dx,dy);
            document.close();
            out.close();
        } catch (Exception e) {
            throw new ExcepcionErrorInterno(CodigoError.ERROR_INTERNO, e);
        }
    }

    /**
     * Genera un documento sellado en fichero pdf.
     * 
     * @param input
     *            Fichero pdf de entrada
     * @param texto
     * @param textoQR
     * @param codFirma
     * @param out
     * @throws ExcepcionErrorInterno
     */
    public void firmarPDFSellado(InputStream input, String texto, String texto2Line, String textoQR,
            String codFirma, OutputStream out) throws ExcepcionErrorInterno {
        // leemos el pdf utilizando iText.
        try {
            // Recuperamos el documento original
            PdfReader reader = new PdfReader(input);

            // Obtenemos el tamao de la pgina
            Rectangle pageSize = reader.getPageSize(1);

            // Creamos un nuevo documento del mismo tamao que el original
            Document document = new Document(pageSize);

            // creo una instancia para escritura en el documento
            PdfWriter writer = PdfWriter.getInstance(document, out);
            document.open();

            // Aadimos al documento la imagen de cabecera y de pie
            float altoCabeceraYPie = addContent(texto, texto2Line, textoQR, codFirma, document, pageSize, true,
                    true);
            altoCabeceraYPie = altoCabeceraYPie + document.topMargin();
            // insertamos la portada del documento que estamos firmando.
            float escala = (pageSize.getHeight() - altoCabeceraYPie) / pageSize.getHeight();

            PdfContentByte cb = writer.getDirectContent();

            PdfImportedPage portada = writer.getImportedPage(reader, 1);
            cb.setRGBColorStroke(0xCC, 0xCC, 0xCC);
            cb.transform(AffineTransform.getScaleInstance(escala, escala));
            cb.transform(AffineTransform.getTranslateInstance(document.leftMargin() * 2.5,
                    ALTO_ETIQUETA + document.topMargin()));

            cb.addTemplate(portada, 0, 0);// , escala, 0, 0, escala,dx,dy);
            document.close();
            out.close();
        } catch (Exception e) {
            throw new ExcepcionErrorInterno(CodigoError.ERROR_INTERNO, e);
        }
    }

    /**
     * Genera codigo de firma para impresin formado por el texto, el cdigo qr
     * del texto y un cdigo de barras con el cdigo de firma.
     * 
     * @param text
     * @param codFirma
     * @return
     * @throws ExcepcionErrorInterno
     */
    public byte[] generate(String text, String url, String textoQR, String codFirma) throws ExcepcionErrorInterno {
        // Comprobamos el tamao del texto. No recomendamos textos de mas de 120
        // caracteres
        if (textoQR.length() > MAX_TEXT_SIZE) {
            log.warn("El tamao del texto '" + text + "' ha excedido el tamao mximo. " + text.length() + ">"
                    + MAX_TEXT_SIZE);
            textoQR = textoQR.substring(textoQR.lastIndexOf(" "));
            log.warn("El texto sera recortado: '" + textoQR + "'");
        }

        // Generamos la sufperficie de dibujo
        BufferedImage imagenQR = new BufferedImage(ANCHO_ETIQUETA, ALTO_ETIQUETA, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = imagenQR.createGraphics();
        // El fondo es blanco
        g.setBackground(Color.WHITE);
        g.clearRect(0, 0, ANCHO_ETIQUETA, ALTO_ETIQUETA);
        g.setColor(Color.BLACK);
        g.setStroke(new BasicStroke(2f));
        // Pintamos la caja de borde
        g.draw(new java.awt.Rectangle(MARGEN_CAJA, MARGEN_CAJA, ANCHO_ETIQUETA - MARGEN_CAJA * 2,
                ALTO_ETIQUETA - MARGEN_CAJA * 2));
        g.draw(new java.awt.Rectangle(MARGEN_CAJA + 3, MARGEN_CAJA + 3, ANCHO_ETIQUETA - MARGEN_CAJA * 2 - 6,
                ALTO_ETIQUETA - MARGEN_CAJA * 2 - 6));

        // Generamos el cdigo QR
        Qrcode x = new Qrcode();
        x.setQrcodeErrorCorrect('L');
        x.setQrcodeEncodeMode('B'); // Modo Binario
        byte[] d = textoQR.getBytes();
        // Generamos el cdigo QR
        boolean[][] s = x.calQrcode(d);
        for (int i = 0; i < s.length; i++) {
            for (int j = 0; j < s.length; j++) {
                if (s[j][i]) {
                    g.fillRect(j * SIZE_QR + MARGEN, i * SIZE_QR + MARGEN, SIZE_QR, SIZE_QR);
                }
            }
        }
        int marjenSeparador = MARGEN + SIZE_QR * s.length + MARGEN_CAJA;
        int marjenTexto = marjenSeparador + MARGEN_CAJA;
        // Linea de separacin entre el QR cdigo y el texto
        g.drawLine(marjenSeparador - 3, MARGEN_CAJA + 3, marjenSeparador - 3, ALTO_ETIQUETA - MARGEN_CAJA - 3);
        g.drawLine(marjenSeparador, MARGEN_CAJA + 3, marjenSeparador, ALTO_ETIQUETA - MARGEN_CAJA - 3);

        // Linea de separacin entre texto y cdigo de barras.
        int marjenCodigoBarras = MARGEN + MAX_ROWS * FONT_SIZE;
        // Pintamos una pequea linea de separacin
        g.drawLine(marjenSeparador, marjenCodigoBarras, ANCHO_ETIQUETA - MARGEN_CAJA - 3, marjenCodigoBarras);
        g.drawLine(marjenSeparador, marjenCodigoBarras - 3, ANCHO_ETIQUETA - MARGEN_CAJA - 3,
                marjenCodigoBarras - 3);

        // Escribimos el texto
        List<String> parrafos = split(text);

        g.setFont(new Font(g.getFont().getName(), Font.BOLD, FONT_SIZE));

        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        for (int i = 0; i < parrafos.size(); i++) {
            String linea = parrafos.get(i);
            // Pintamos la nueva linea de texto
            g.drawString(linea, marjenTexto, MARGEN + 3 + (FONT_SIZE * i + 1));
        }

        g.setFont(new Font(g.getFont().getName(), Font.BOLD, FONT_SIZE - 4));
        // Pintamos el texto final de una sola lnea( Generalmente el MD5 )
        //if(url.length())
        if (!url.equals("")) {
            g.drawString("Custodia del documento: ", marjenTexto, marjenCodigoBarras - 10 * 2);
            g.drawString(url, marjenTexto, marjenCodigoBarras - 6);
        }
        marjenCodigoBarras += MARGEN_CAJA;
        // Generamos el cdigo de barras
        try {
            // int marjenCodigoBarras=MARGEN+MAX_ROWS*FONT_SIZE;
            BarcodeFactory.createCode39(codFirma, true).draw(g, marjenTexto, marjenCodigoBarras);
        } catch (Exception e1) { // TODO
            e1.printStackTrace();
        }
        // g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
        // RenderingHints.VALUE_ANTIALIAS_OFF);

        // Finalizamos la superficie de dibujo
        g.dispose();
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        // Generamos la imagen
        try {
            ImageIO.write(imagenQR, "png", out);
            // ImageIO.write(imagenQR, "png", new
            // FileOutputStream("/tmp/imagen.png"));
            out.close();
        } catch (Exception e) {
            throw new ExcepcionErrorInterno(CodigoError.ERROR_INTERNO, e);
        }

        return out.toByteArray();
    }

    /**
     * Retorna el texto en parrafos preparados para la etiqueta. Trocea en
     * lineas y en "," si el tamao de la linea es excesivo.
     * 
     * @param text
     *            texto a procesar
     * @return Listado de lineas.
     */
    private static List<String> split(String text) {
        String[] parrafos = text.split("\\n");
        List<String> parrafosProcesados = new ArrayList<String>();
        for (int i = 0; i < parrafos.length; i++) {
            String linea = parrafos[i];
            if (linea.length() > MAX_SIZE_LINEA + 1) {
                // La linea es demasiado grande, troceamos
                StringBuilder builder = new StringBuilder();
                String seccion[] = linea.split(",");
                for (int j = 0; j < seccion.length; j++) {
                    String temp = seccion[j];

                    if (builder.length() + temp.length() > MAX_SIZE_LINEA) {
                        builder.append(",");
                        parrafosProcesados.add(builder.toString());
                        builder = new StringBuilder();
                    }
                    builder.append(temp);

                }
                parrafosProcesados.add(builder.toString());
            } else {
                parrafosProcesados.add(linea);
            }
        }
        return parrafosProcesados;
    }
    /*
    Cdigo de firma: A1HH-EZH9T5E1-1889-3802-8854
    DIRECCION GENERAL DE IMPUESTOS INTERNOS
    RNC: 123 456 789 Email: oficinavirtual@dgii.gov.do
        
    Custodia del documento: http://viafirma.viavansi.com/viafirma/v/A1HH-EZH9T5E1-1889-3802-8854
     */

    /** Crea los textos utilizados en la etiqueta de justificante de firma.
     * @param certificado Array con el texto y el texto QR
     * @param sha1 cdigo del documento sha1
     * @param url Url del servicio de verificacin.
     * @return
     */
    public String[] buildTextoEtiqueta(CertificadoGenerico certificado, String codFirma, String url, String sha1) {
        StringBuilder texto = new StringBuilder();
        StringBuilder textoQR = new StringBuilder();
        // TODO Recuperamos el plugin asociado a este tipo de certificados.
        AuthoritySupportPlugin support;
        try {
            support = PluginLocator.getAuthoritySupport(certificado);

            // Indicamos el la etiqueta el cdigo de firma.
            texto.append("Cdigo de firma: ").append(codFirma).append("\n");
            texto.append(sha1).append("\n");
            textoQR.append(codFirma).append("\n");

            support.appendEtiqueta(texto, certificado, codFirma);
            support.appendEtiquetaSimple(textoQR, certificado, codFirma);
            textoQR.append(url);

        } catch (ExcepcionErrorInterno e) {
            log.fatal("Tipo de certificado no reconocido", e);
        }

        // Autoridad de Certificacin
        /*if (!StringUtils.isEmpty(certificado.getCa())) {
           String[] textCertificado = certificado.getCa().split(",");
           StringBuilder linea = new StringBuilder();
           StringBuilder ca = new StringBuilder();
           for (int i = 0; i < textCertificado.length; i++) {
        String cadena = textCertificado[i];
        // Nmero mximo de caracteres por lnea
        if (linea.length() + cadena.length() > 80) {
           ca.append(linea);
           ca.append("\n");
           linea = new StringBuilder(cadena);
        } else {
           linea.append(cadena);
           if (i < textCertificado.length - 1) {
              linea.append(",");
           }
        }
           }
           ca.append(linea);
           texto.append(ca);
        }
         */

        return new String[] { texto.toString(), textoQR.toString() };
    }

    /**
     * Compone la url correcta a mostrar en el resguardo de la firma.
     * 
     * @param url
     * @return
     */
    public String buildFriendlyURLIfNeeded(String url) {
        if (doUrlFriendly) {
            if (urlFriendly != null) {
                url = url.substring(url.indexOf("/v/"));
                url = urlFriendly + url;
                return url;
            } else {
                return url;
            }
        }
        return "";
    }
}