Compactação de PDF utilizando em Java com iText

Escrito em 11/12/2018 15:51 por Benigno

Olá programador, tudo bem?

Muitas vezes, ao trabalharmos em grandes projetos, nos deparamos com a necessidade de manipularmos arquivos PDF. Se você esta lendo esse tutorial, com certeza, já sabe que a sigla inglesa PDF significa Portable Document Format (Formato Portátil de Documento), um formato de arquivo criado pela empresa Adobe Systems para que qualquer documento seja visualizado, independente de qual tenha sido o programa que o originou.

Quando utilizamos esses arquivos em nossos projetos, em algumas situações, eles podem ter tamanhos exagerados, por causa da utilização de imagens com alta resolução ou mesmo de algum outro recurso utilizado que o deixa mais “pesado”. Para que consigamos um arquivo mais “enxuto” e leve, temos um artifício muito interessante com a API iText.

O projeto desse tutorial foi feito com:

Utilizaremos o componente p:fileupload e p:filedownload para enviarmos o arquivo ao servidor e para recebermos o download do arquivo após a compactação.

Nossa classe principal, que irá gerenciar a view ficou da seguinte forma:

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package br.com.benignosales.manipuladorpdf.beans;

import com.itextpdf.text.DocumentException;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.PdfWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.view.ViewScoped;
import javax.inject.Named;
import org.primefaces.model.DefaultStreamedContent;
import org.primefaces.model.StreamedContent;
import org.primefaces.model.UploadedFile;

/**
 *
 * @author benignosales
 */
@Named
@ViewScoped
public class PDFBean implements Serializable {

    private UploadedFile uploadedFile;
    private final String OUTPUT_FILE = "/tmp/arquivoCompactado.pdf";
    private StreamedContent downloadFile;
    private String tamanhoArquivoEntrada;
    private String tamanhoArquivoSaida;
    private static final Logger LOG = Logger.getLogger(PDFBean.class.getName());

    public void compactar() {
        InputStream is = null;
        try {
            if (uploadedFile.getFileName().isEmpty()) {
                throw new FileNotFoundException();
            }
            is = uploadedFile.getInputstream();
            PdfReader reader = new PdfReader(is);
            PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(new File(OUTPUT_FILE)), PdfWriter.VERSION_1_5);
            stamper.setFullCompression(); //método para compactar o arquivo            
            stamper.close();
            PdfReader newReader = new PdfReader(OUTPUT_FILE);
            tamanhoArquivoSaida = (newReader.getFileLength() / 1024 + "Kb");
            tamanhoArquivoEntrada = uploadedFile.getSize() / 1024 + "Kb";
            InputStream stream = new FileInputStream(new File(OUTPUT_FILE));
            downloadFile = new DefaultStreamedContent(stream, "application/pdf", uploadedFile.getFileName());
            FacesMessage message = new FacesMessage("Sucesso", uploadedFile.getFileName() + " Compactado.");
            FacesContext.getCurrentInstance().addMessage(null, message);
        } catch (FileNotFoundException ex) {
            LOG.log(Level.SEVERE, null, ex);
            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Falha", "Selecione um arquivo");
            FacesContext.getCurrentInstance().addMessage(null, message);
        } catch (IOException | DocumentException ex) {
            LOG.log(Level.SEVERE, null, ex);
        } finally {
            try {
                if (is != null) {
                    is.close();
                }
            } catch (IOException ex) {
                LOG.log(Level.SEVERE, null, ex);
            }
        }
    }
}

Foram suprimidos os getters e setters. Temos um único método, no qual pegamos o arquivo enviado, realizamos sua leitura com o PDFReader, em seguida, através da classe PDFStamper, que deve ser utilizada sempre que quisermos modificar o PDF que estivermos lendo, iremos aplicar uma compactação através do método setFullCompression. No final do método, após a compactação, iremos ler o novo arquivo, capturar seu tamanho e criar um StreamedContent para que fique disponível para donwload através do componente p:fileDownload.

A view, com todos os componentes vinculados à classe PDFBean é descrita abaixo:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"    
      xmlns:p="http://primefaces.org/ui"
      xmlns:h="http://xmlns.jcp.org/jsf/html">
    <h:head>
        <title>Manipulação de PDF</title>
        <style>
            body {
                font-family: verdana;
            }

            .label {
                font-weight: bold;
                font-size: 1.2em;
            }

            .numbers {
                font-family: courier;
            }

            input[type=file] {
                font-family: verdana;
                font-size: 1em;
                color: red;
            }
        </style>
    </h:head>
    <h:body>
        <p:panel header="Manipulação de PDF" style="width: 50%">
            <p:messages showDetail="true"/>
            <h:form enctype="multipart/form-data">
                <h:outputText value="Selecione um PDF:" styleClass="label"/>
                <h:panelGrid columns="2">                
                    <p:fileUpload id="fileUpload" label="Selecionar um Arquivo" value="#{PDFBean.uploadedFile}" mode="simple" allowTypes="/(\.|\/)(pdf)$/"/>                
                </h:panelGrid>            
                <p:commandButton value="Compactar Arquivo" action="#{PDFBean.compactar()}" style="margin:10px 0 10px 0" ajax="false" icon="ui-icon ui-icon-arrow-4-diag"/>
                <h:panelGrid columns="2">
                    <h:outputText value="Tamanho Orignal:" styleClass="label"/>
                    <h:outputText value="#{PDFBean.tamanhoArquivoEntrada}" styleClass="numbers"/>            
                    <h:outputText value="Tamanho Compactado:" styleClass="label"/>
                    <h:outputText value="#{PDFBean.tamanhoArquivoSaida}" styleClass="numbers"/>
                </h:panelGrid>
                <p:commandButton value="Download" disabled="#{empty(PDFBean.uploadedFile.fileName)}" ajax="false">
                    <p:fileDownload value="#{PDFBean.downloadFile}"/>
                </p:commandButton>
            </h:form>

        </p:panel>
    </h:body>
</html>

Na página, definimos um componente p:fileupload simples e não ajax(fizemos isso por opção, portanto você poderá utilizar, caso queira, o componente ajax). É importante, configurar o web.xml com o filtro do primefaces file uploader, conforme código abaixo:

<filter>
        <filter-name>PrimeFaces FileUpload Filter</filter-name>
        <filter-class>org.primefaces.webapp.filter.FileUploadFilter</filter-class>
        <init-param>
            <param-name>thresholdSize</param-name>
            <param-value>10240</param-value>
        </init-param>
        <init-param>
            <param-name>uploadDirectory</param-name>
            <param-value>/tmp/</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>PrimeFaces FileUpload Filter</filter-name>
        <servlet-name>Faces Servlet</servlet-name>
        <dispatcher>FORWARD</dispatcher>
    </filter-mapping>

Após isso, podemos ver o resultado no vídeo abaixo:

É isso, fica a dica. Dependendo do arquivo, conseguimos diminuir entre 10 e 20% do seu tamanho. Valeu!