O iText é a biblioteca Java mais importante para manipulação de arquivos no formato PDF, muito utilizada em diversas organizações que precisam trabalhar com diferentes tipo e complexidades de relatórios. O iText é open source e seu primeiro lançamento ocorreu em 14 de fevereiro de 2000. O projeto se popularizou no ano de 2008 e em 2009 a licença original MPL/LGPL foi alterada para AGPL. Dessa forma, devemos comprar uma licença comercial em caso de desenvolvermos atividades comerciais envolvendo o iText.

Não obstante, iText também foi portado para o framework .NET da Microsoft e ganhou o nome iTextSharp.

Para baixar o iText basta visitar o site oficial.

Veremos como podemos utilizar o objeto PdfReader e como aumentar a sua performance em aplicações comerciais que muitas vezes já estão com a performance quase estagnadas.

Obtendo Arquivos PDF Existentes

Primeiramente vamos verificar como podemos obter informações sobre o documento que estamos processando. Perguntas como quantas páginas possui o documento? Qual o tamanho da página? Além de diversas outras perguntas podem ser respondidas usando o objeto PDFReader. Segue na Listagem 1 um código de exemplo de como podemos utilizar o objeto.

Listagem 1. Exemplo de uso do objeto PDFReader


  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.PrintWriter;
   
  import com.itextpdf.text.DocumentException;
  import com.itextpdf.text.Rectangle;
  import com.itextpdf.text.pdf.PdfReader;
   
   
  public class ObjetoPDFReader {
   
         public static final String RESULT = 
          "C:\\Users\\higor\\Desktop\\proj_iText\\arqPDFexemploResult.txt";
         
         public static void main(String[] args) 
          throws DocumentException, IOException {
   
               String filename = 
                "C:\\Users\\higor\\Desktop\\proj_iText\\arqPDFexemplo.pdf";
               
               PrintWriter writer = new PrintWriter(new 
                 FileOutputStream(RESULT));
               PdfReader reader = new PdfReader(filename);
               writer.println(filename);
               writer.print("Numero de paginas: ");
               writer.println(reader.getNumberOfPages());
               Rectangle mediabox = reader.getPageSize(1);
               writer.print("Tamanho da pagina 1: [");
               writer.print(mediabox.getLeft());
               writer.print(',');
               writer.print(mediabox.getBottom());
               writer.print(',');
               writer.print(mediabox.getRight());
               writer.print(',');
               writer.print(mediabox.getTop());
               writer.println("]");
               writer.print("Rotacao da primeira pagina: ");
               writer.println(reader.getPageRotation(1));
               writer.print("Tamanho da rotacao (pagina 1): ");
               writer.println(reader.getPageSizeWithRotation(1));
               writer.print("Tamanho do arquivo: ");
               writer.println(reader.getFileLength());
               writer.print("Esta reprocessado? ");
               writer.println(reader.isRebuilt());
               writer.print("Esta encriptado? ");
               writer.println(reader.isEncrypted());
               writer.println();
               writer.flush();
               writer.close();
               
         }
   }

Não podemos nos esquecer de alterar os caminhos para as pastas e arquivos. Neste exemplo estamos utilizando pastas e arquivos PDF pessoais.

Segue na Figura 1 o resultado retornado da execução do código anterior.

Figura 1. Resultado da execução da Listagem 1

Uma ressalva importante a ser feita é que quando tentarmos abrir um arquivo PDF corrompido receberemos uma mensagem "There was an error opening this document. The file is damaged and could not be repaired". Se tentarmos abrir na listagem acima esse mesmo arquivo corrompido, teremos como resultado o lançamento de uma exceção InvalidPdfException com a seguinte mensagem: “Rebuild failed: trailer not found; original message: PDF startxref not found.”. O arquivo está corrompido e nada pode ser feito. Dessa forma, teríamos que contatar o responsável pelo arquivo e solicitar uma versão funcional. O método isRebuilt() é utilizado para checar se um PDF precisa de reparos.

Outro problema encontrado é quando tentamos ler arquivos PDF encriptados. Nos casos em que um arquivo PDF estiver protegido por senha devemos fornecer a senha antes de abrir o documento ou receberemos uma exceção BadPasswordException.

Reduzindo o Uso de Memória com PdfReader

A maioria dos desenvolvedores criam uma instância do objeto PdfReader usando uma String que representa o caminho de um arquivo PDF existente. Usando o construtor do PdfReader fará com que o PdfReader carregue muita coisa nos objetos Java que estarão em memória.

Isto pode ser um exagero para arquivos grandes, especialmente quando estamos interessados apenas em partes do documento. Assim, podemos escolher ler um PDF parcialmente. Por exemplo, podemos imaginar que temos um arquivo PDF com 1000 páginas, mas estamos interessados apenas na primeira página deste documento. Podemos evitar o uso do construtor. O código da Listagem 2 mostra como poderíamos fazer isso.

Listagem 2. Exemplo utilizando RandomAcessFileOrArray


  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.PrintWriter;
   
  import com.itextpdf.text.DocumentException;
  import com.itextpdf.text.io.RandomAccessSource;
  import com.itextpdf.text.io.RandomAccessSourceFactory;
  import com.itextpdf.text.pdf.PdfReader;
  import com.itextpdf.text.pdf.RandomAccessFileOrArray;
   
   
  public class ObjetoPDFReaderComLeituraParcial {
         
         public static void main(String[] args) throws DocumentException, IOException {
   
               String filename = 
               "C:\\Users\\higor\\Desktop\\proj_iText\\android.pdf";
               
               RandomAccessSourceFactory f = new RandomAccessSourceFactory();
               RandomAccessSource randomAccessSource = 
                f.createBestSource(filename);
               
               PdfReader reader = new PdfReader(
                  new RandomAccessFileOrArray(randomAccessSource), null);
               
               System.out.println(reader.getNumberOfPages());
               
         }
         
  }

Se quisermos fazer uma análise mais profunda, podemos analisar a memória utilizada neste exemplo e num exemplo utilizando apenas PdfReader. Utilizando o construtor do PdfReader temos um aumento em mais de oite vezes no total de memória utilizada para guardar o pdf.

Dessa forma estamos usando muito menos memória do que se estivéssemos utilizando toda a memória como é feito com PdfReader. Portanto se estivermos utilizando um documento muito grande devemos considerar o uso de PdfReader com um RandomAccessFileOrArray.

Porém, também existe outra forma de reduzir a quantidade de memória usada que é simplesmente reduzindo o número de página que gostaríamos de trabalhar. Para isso podemos dizer explicitamente ao objeto PdfReader que gostaríamos de trabalhar com as páginas 4 até 8. O código da Listagem 3 demonstra este exemplo.

Listagem 3. Exemplo utilizando número reduzido de páginas


  import java.io.IOException;
  import com.itextpdf.text.DocumentException;
  import com.itextpdf.text.pdf.PdfReader;
   
  public class ObjetoPDFReaderUtilizandoLeituraPaginas {
         
    public static void main(String[] args) throws DocumentException, 
      IOException {
               
         String filename = 
          "C:\\Users\\higor\\Desktop\\proj_iText\\android.pdf";
               
         PdfReader reader = new PdfReader(filename);
         reader.selectPages("4-8");
         System.out.println(reader.getNumberOfPages());
       }
  }

Também podemos ter múltiplos intervalos de páginas separadas por vírgulas. Outro modificador que pode ser utilizado é o "!" que remove páginas que já foram selecionadas. Ainda podemos utilizar "o" (odd) para páginas ímpares ou “e” (even) para páginas pares. Segue na Listagem 4 um exemplo de alguns dos parâmetros discutidos.

Listagem 4. Exemplo utilizando parâmetros do método selectPages()


  import java.io.IOException;
   
  import com.itextpdf.text.DocumentException;
  import com.itextpdf.text.pdf.PdfReader;
   
   
  public class ObjetoPDFReaderUtilizandoRangesParesImpares {
         public static void main(String[] args) throws DocumentException, IOException {
               
               String filename = "C:\\Users\\higor\\Desktop\\proj_iText\\android.pdf";
               
               PdfReader reader = new PdfReader(filename);
               reader.selectPages("o");
               System.out.println(reader.getNumberOfPages());
               reader.selectPages("e");
               System.out.println(reader.getNumberOfPages());
               reader.selectPages("4-8,8-10");
               System.out.println(reader.getNumberOfPages()); 
         }
  }

Se tentarmos passar algumas páginas fora do range de páginas teremos um NullPointerException. Uma dica é sempre tratar os valores de entrada quando estamos lidando com informações providas pelos usuários, isso ajuda a evitar um crash da aplicação em tempo de execução.

Concluindo, vimos como utilizar o PdfReader e como podemos aumentar a sua performance, tornando as nossas aplicações mais competitivas no mercado e utilizando com eficiência os recursos dos servidores que muitas vezes já estão bastante sobrecarregados com diversas outras ferramentas concorrentes.

Bibliografia

[1] Eclipse Foundation Tutorial, disponível em http://www.eclipse.org/tutorial

[2] LOWAGIE, B. iText in Action, Second Edition. Manning, 2011.

[3] iText PDF Library. Disponível em http://sourceforge.net/projects/itext/