quarta-feira, 4 de maio de 2011

iTextSharp (parte 2) - PDF: Imagens e tabelas em C#

[update]
Gostaria de salientar que este post e o anterior contêm apenas explicações. Nada de código. Se, por algum motivo, houver preferência por ver apenas um código de exemplo do uso do iTextSharp, sugiro ir direto para o terceiro post, mas acho que a leitura das explicações pode ajudar bastante.
[/update]

Retomando o assunto de criação de pdf em C# com o iTextSharp.
Se você não está entendendo o que está acontecendo você provavelmente deveria começar a leitura pelo primeiro post.

Imagens
Para trabalhar com imagens eu usei combinações entre a classe System.Drawing.Image e a classe Image do iTextSharp.
Antes de mais nada é preciso entender que eu fiz uso de imagens que eu havia adicionado aos Recursos do meu projeto. Pra quem não souber como fazer é bom ver o post do blog que fala sobre isso. Pode acessar aqui.

Eis o que eu fiz a partir disso.
Defini um objeto System.Drawing.Image chamado imagemTemporaria.
Ele receberá cada imagem antes de passá-la para a classe Image do iTextSharp.
Serão duas imagens.

Primeiro eu instancio o objeto imagemTemporaria usando um new System.Drawing.Bitmap e passando a imagem que está nos Recursos como parâmetro.
Depois crio uma nova instância da classe Image do iTextSharp usando Image.GetInstance e passando por parâmetros o objeto imagemTemporaria e um objeto indicando o formato. Como no meu caso é um arquivo PNG, o segundo parâmetro será System.Drawing.Imaging.ImageFormat.Png.
Feito isso eu ajusto a propriedade Alignment da minha imagem.
Existem várias combinações possíveis, então não vou entrar em detalhes. Cada um pode fazer os testes necessários.
Eu farei uso da combinação Image.LEFT_ALIGN e Image.TEXTWRAP, usando o operador | de forma semelhante ao que é feito nos estilos para a classe Font, que eu mostrei nos post anterior.
Por fim ajusto a escala da imagem se necessário, utilizando o metódo ScalePercent, passando o valor percentual como parâmetro.

Repito isso para minhas duas imagens.

Tabelas
Para criar uma tabela usarei duas classes, além da classe Phrase, que já abordei anteriormente.
PdfPTable é a classe da tabela em si e usarei várias PdfPCell para definir as células da tabela.
É útil quando cada célula tem conteúdos diferentes. Se for construir uma tabela vazia, por exemplo, é desnecessário o uso de várias PdfPCell.

Primeiro defino um objeto da classe PdfPTable com duas colunas, passando um inteiro, que se refere ao número de colunas, como parâmetro para o construtor.
Depois defino a propriedade HorizontalAlignment como Image.ALIGN_LEFT, para que a tabela fique à esquerda da página. Normalmente ela ficaria centralizada.
Então uso o método SetWidths para ajsutar a largura de cada coluna da tabela. O parâmetro a ser passado para o método é um vetor de floats e o número de valores deve ser igual ao número de colunas. No meu caso é um vetor de duas posições, já que defini duas colunas. Essa parte também poderia ser feita na hora em que se instancia a classe. Há uma sobrecarga do construtor com 2 parâmetros, o primeiro é o número de colunas e o segundo é um vetor com as larguras das colunas.

A partir daí trabalho com as células.
Defino um objeto PdfPCell genérico. Utilizo ele para todas as células. Apenas preciso gerar uma nova instância cada vez que quero uma nova célula.
Com o objeto PdfPCell instanciado, uso o método AddElement para adicionar objetos Phrase, Image, etc.
Depois uso o método AddCell da tabela que eu havia criado anteriormente, passando a célula como parâmetro.
Repito isto para cada nova célula.

Uma propriedade interessante da classe PdfPCell é a Border, onde é possível definir o estilo da borda da célula. Definirei 2 células sem borda para exemplificar, definindo a propriedade como Image.NO_BORDER.

Pausa para um café
Quê? "Pausa para um café" é um tempo pra pensar, uma interrupção na sequência de explicações do post para falar de um assunto que pode não estar diretamente relacionado, mas que considero relevante.

Por quê? Bom. Eu tive um problema no mínimo interessante aqui, que me fez lembrar que é preciso estar atento, pois as soluções nem sempre vão estar no lugar que você estava procurando, ou onde você esperava que ela estivesse, pois por mais sistemático que um raciocínio possa ser, duas pessoas diferentes sempre podem organizar a mesma idéia de forma diferente.

Eis a questão. Eu queria diminuir a altura da linha da tabela. "Simples, não?" Foi o que eu pensei. Mas isso me rendeu algumas horas de busca. Minha ideia era: "Deve haver uma forma de deixar todas as linhas da tabela com o mesmo tamanho, então deve ser na classe da tabela." Procurei pela PdfPTable e nada. Depois seguindo a lógica fui pra classe de linhas, a qual eu nem mencionei aqui. Também não encontrei nada. Procurei na PdfPCell e nada. No meio dessa busca me deparei por vezes com propriedades que se chamavam Leading ou derivações disso, que era o que eu imaginava que serviria para definir o tamanho das linhas, mas todas elas só suportavam get, nada de set.

Nessa hora, continuar quebrando a cabeça pode ser desastroso. A tendência de se aprofundar numa busca errônea aumenta conforme tua busca progride. Parar e relaxar um pouco pode ajudar bastante.

Eu já sabia que em todas as classes referentes à tabela e suas partes não havia nada, ou ao menos eu não havia encontrado. Então tive a ideia de tentar mecher não na tabela em si, mas nos objetos Phrase que eu havia adicionado em cada célula. Passei a usar o construtor da classe Phrase que, além dos parâmetros string e Font, tinha o parâmetro float leading no início. Isso, aquele mesmo, do qual eu falei lá na parte sobre a classe Phrase e que eu disse "Ele pode ser útil às vezes, mas não vou falar sobre isso agora".
Nesse caso eu nem preciso dizer o quão útil foi, preciso? Bom. Funcionou, com isso eu consegui diminuir a altura da minha linha. No final das contas ela estava atrelada a altura das coisas que a célula continha. Faz sentido, não? Pois é, agora faz, mas não foi meu primeiro pensamento.

Continua...

2 comentários:

junior_luiz@hotmail.com disse...

Ivan, esou tentando gerar um relatório com um titulo e um subtitulo e uma tabela com 6 colunas quando a categoria for diferente quero totalizar e gerar um paragrafo informando o nome da categoria e outra tabela com 6 colunas tb e no final o total dessa categoria e um total geral, só tenho duas categorias, como eu faço isso, adiciono duas tabelas ?

_ivan disse...

A forma como tu faz pode depender um pouco da forma como as tuas classes estão estruturadas e como tu está fazendo essa separação
em categorias, mas sim, o ideal é tu ter uma tabela por categoria, pra que tu possa inserir o parágrafo que tu queres entre elas. É como eu faria.

Tu disseste que têm apenas duas categorias. Não crie tua lógica baseando-se muito
nisso. Procure fazer algo que insira cada categoria isoladamente, pois, se eventualmente
tu precisares inserir mais categorias, não precisarás modificar tua lógica.