Por que eu devo ler este artigo:Lidar com PDF em aplicações Delphi é uma tarefa bastante comum, seja para armazenamento, leitura ou geração de arquivos para posterior envio por e-mail, por exemplo. Nessas situações é fundamental que o arquivo tenha o menor tamanho possível, de forma que se possa economizar espaço em banco de dados e reduzir o tráfego de rede. O GhostScript, tema central desse artigo, é importante porque além de prover a funcionalidade de compressão de arquivos PDF, também permite extrair dados de um arquivo e gerar outros a partir de extensões diferentes.

Ao trabalhar com leitura e gravação de arquivos em sistemas, é comum o surgimento de alguns problemas, principalmente relacionados ao armazenamento e, em muitos casos, também com o tempo que esse arquivo leva para trafegar na rede. Os usuários, ao inserirem um documento na aplicação, não estão preocupados com o quanto esse documento irá ocupar em espaço no disco, na verdade muitos deles nem têm essa noção. Cabe ao desenvolvedor entender, identificar e resolver esses problemas, não podendo, muitas vezes, simplesmente gravar em disco diretamente ou em um banco de dados um arquivo inserido pelo usuário, o que pode causar no futuro problemas como excesso de espaço utilizado em disco e perda de desempenho.

Para solucionar, ou ao menos reduzir esse tipo de problema, vamos utilizar uma ferramenta chamada GhostScript, que pode ser integrada a aplicações Delphi e permite, além de comprimir arquivos PDF, reduzindo seu tamanho em disco, extrair dados desse tipo de arquivo, bem como gerar PDFs a partir de uma extensão diferente.

Problemas com arquivos grandes

Quando lidamos com arquivos grandes em nossas aplicações, as principais dificuldades surgem quando se torna necessário transmitir esses arquivos através da internet, seja por e-mail ou via webservice, por exemplo. Em situações mais críticas, pode haver ainda limitações de largura de banda disponível e tráfego máximo permitido por período, muito comum em pacotes de hospedagem de serviços web.

Outra grande dificuldade diz respeito ao espaço de armazenamento físico utilizado e disponível, que tende a ser consumido rapidamente dependendo da frequência com que arquivos são armazenados. Em alguns casos há a necessidade de efetuar melhorias de hardware para suportar o grande volume de dados, situação que pode deixar a aplicação fora de funcionamento por algum tempo, dependendo da infraestrutura da empresa.

Quando esses arquivos são armazenados em banco de dados começam a surgir problemas de desempenho nas consultas e principalmente nas gravações desses registros, além de afetar diretamente a realização dos backups, uma vez que existirá um gasto maior de tempo e espaço para cópia dos mesmos.

Conhecendo o PDF

O PDF (Portable Document Format) é um formato de arquivo desenvolvido pela Adobe Systems com o objetivo representar documentos de forma independente do aplicativo, do hardware e do sistema operacional usados para criá-los. Um arquivo PDF pode descrever documentos que contenham texto, gráficos e imagens em um formato compatível com a maior parte dos sistemas operacionais e que podem ser visualizados em diversas aplicações, inclusive browsers.

É possível gerar arquivos em PDF a partir de vários formatos de documentos, como ODF (LibreOffice) ou DOC (Microsoft Word), além de imagens, como JEG e PNG. No entanto, a qualidade do documento gerado, no que se refere à exibição do conteúdo, pode variar de acordo com o formato do arquivo matriz a partir do qual o PDF foi criado. Portanto, a escolha do formato mais adequado pode ser um esforço válido, principalmente quando se trata de PDFs que contêm informações institucionais a serem entregues a gestores ou clientes e que poderão ser visualizados em dispositivos de diferentes resoluções.

Sobre o GhostScript

O Ghostscript foi escrito originalmente como um processador de imagem raster (RIP) e pode ser utilizado também como conversor de formato de arquivos, como o PostScript para PDF (GhostPDF), ou como uma “impressora virtual”, suportando PDF/A-1 e PDF/A-2 (BOX 1). O GhostScript pode ser utilizado a partir do Prompt de Comandos, com chamadas ao seu executável e passagens de parâmetros ou com a inserção de parâmetro em sua própria interface. Ele também disponibiliza uma API (nesse caso uma DLL) que podemos importar para os sistemas e efetuar chamadas diretamente às suas funções, sem a necessidade de instalação de novos recursos.

O padrão PDF/A, também conhecido como ISO 19005-1, é um padrão para manter informações armazenadas em documentos eletrônicos por algum período de tempo. Basicamente cobre aspectos referentes a reprodução, armazenamento e dependência de hardware e software. Sua versão mais recente (A-2) oferece mais suporte a novas tecnologias e se apoia na versão 1.7 do PDF.

Escrito originalmente por L. Peter Deutsch para o Projeto GNU, o GhostScript foi liberado sob a GNU General Public License em 1986. Atualmente a ferramenta é propriedade de Artifex Software e mantida por seus funcionários e pela comunidade de utilizadores em todo o mundo.

Por que utilizar

Entre as principais ferramentas de compressão de arquivos PDF existentes no mercado atualmente, podemos observar que das seis ferramentas mais utilizadas (CutePDF Writer, doPDF, PrimoPDF, PDF Printer Community, FreePDF e PDF Creator), cinco utilizam o GhostScript. Essas ferramentas, na sua maioria, não criaram nenhuma funcionalidade extra especial, apenas utilizam a API do GhostScript e disponibilizam uma interface amigável para o usuário final. O GhostScript não só trabalha com o formato PDF, mas também com PCL (Printer Command Language), XPS (XML Paper Specification) e SVG (Scalable Vector Graphics). Neste artigo, porém, veremos apenas a parte do PostScript, que é a biblioteca responsável pela manipulação de PDF. Ela conta com uma boa documentação que pode ser consultada para que se possa aproveitar ao máximo suas funcionalidades (ver seção Links).

O que é PostScript

PostScript é uma linguagem de programação especializada para visualização de informações, ou uma linguagem de descrição de páginas, originalmente criada para impressão e posteriormente modificada para o uso com monitores (display PostScript).

A linguagem fornece uma máquina de pilha e comandos específicos para o desenho de letras e figuras, incluindo comandos de traçado e formas de representação de imagens. Foi desenvolvida pela Adobe, tendo como inspiração a InterPress, uma linguagem desenvolvida na Xerox.

A PostScript é considerada uma linguagem de programação Turing completa, ou seja, é possível implementar qualquer algoritmo computacional respeitando-se os limites de memória de dados. Tipicamente os programas PostScript não são produzidos por humanos, mas sim por outros programas de computador. É possível, entretanto, produzir gráficos ou cálculos através da programação manual diretamente em PostScript. Trata-se de uma linguagem interpretada, orientada por pilha de dados, com algumas características semelhantes à linguagem Forth, incorporando um grande dinamismo de tipos de dados, com estrutura desses semelhantes à linguagem Lisp. A linguagem utiliza a sintaxe da notação reversa polonesa, onde o uso de parênteses não é necessário.

A programação manual em PostScript exige alguma prática, considerando que o layout da pilha de dados deve ser planejado pelo programador. A maioria dos operadores (funções) extrai os seus argumentos da pilha de dados e, quando é o caso, devolve o resultado na pilha. Dados literais ocasionam o efeito de colocar uma cópia de si mesmo na pilha de dados. Estruturas de dados complexas podem ser construídas através de arrays e entradas de dicionário.

Problemas ao trabalhar com PDF com GhostScript

Ocasionalmente, você pode tentar ler ou imprimir um arquivo PDF que o GhostScript não reconhece como tal, embora o mesmo arquivo possa ser aberto e interpretado por um visualizador Adobe Acrobat Reader, por exemplo. Em muitos casos, isso ocorre quando o PDF é gerado incorretamente. O Acrobat Reader tende a contornar algumas falhas nos arquivos PDF inválidos, já o GhostScript espera que os arquivos tratados estejam em conformidade com a norma do formato. Por exemplo, apesar da regra para arquivos PDF válidos ser começar com %PDF, o Acrobat digitaliza os primeiros 1000 bytes para esta cadeia, e ignorar qualquer lixo anterior.

No passado, a política do Ghostscript era a de simplesmente falhar com uma mensagem de erro quando confrontado com estes arquivos. Esta política, sem dúvida, encorajava geradores de PDF a serem mais cuidadosos. No entanto, este comportamento não é muito amigável para as pessoas que querem apenas usar o GhostScript para visualizar ou imprimir arquivos PDF. A nova política é tentar tornar PDFs corrompidos utilizáveis imprimindo um aviso, de modo que GhostScript continua útil quando se tenta abrir arquivos inválidos.

Um outro problema percebido ao se trabalhar com PostScript é na geração do PDFs. Algumas fontes não são reconhecidas e o documento gerado acaba não refletindo 100% do original. Uma solução é a inserção dessas fontes na hora da geração do PDF, porém isso é muito trabalhoso e se perde a compactação das fontes, gerando um arquivo final muito grande.

Obter e instalar o Ghostscript

A versão não-comercial do GhostScript é distribuída sob licença GNU Affero GPL, que permite uso e distribuição livres e gratuitos (open source).

A distribuição inclui pacotes para Windows prontos para uso, com instaladores distintos para 32 e 64 bits, que podem ser obtidos na página oficial do GhostScript (seção Links).

Basta executar o instalador Windows e seguir o passo-a-passo do assistente. O programa instalador tem nome no formato gs<versão>w32.exe. No local de instalação no Windows, normalmente em C:\Arquivos de programas\gs\, é criada uma pasta gs<versão>\, de acordo com a versão do GhostScript instalada. A pasta bin contém um arquivo com nome gswin32c.exe e outro gswin32.exe, que é o aplicativo de interação, onde iremos adicionar os comandos para o GhostScript processar. Juntamente aos arquivos EXE temos a DLL que iremos utilizar para integração com o Delphi, a gsdll32.dll.

Executando o Ghostscript

O GhostScript pode ser executado de duas formas: através do Prompt de Comandos ou com duplo clique sobre o arquivo gswin32.exe. Ao executar pelo prompt os parâmetros devem ser passados junto à sua chamada, já pelo executável é aberta uma tela onde podemos passar os parâmetros. Para executar através do prompt devemos digitar o seguinte comando: "gswin32 [options] {filename 1}", para isso deve existir um Path configurado para a pasta do GhostScript ou podemos abrir o terminal diretamente pela pasta bin ou colocarmos o caminho mais o nome do executável e os parâmetros no prompt.

Vamos agora começar a usar pequenas funcionalidades dessa biblioteca. Com o Prompt de Comandos aberto vamos digitar a seguinte linha:

gswin32 -dSAFER -dBATCH <caminho de um arquivo pdf> 

Esse comando vai fazer com que o arquivo seja apresentado em uma tela do próprio GhostScript, com isso já podemos eliminar a necessidade do cliente ter um leitor de PDF instalado em sua máquina ou efetuar chamadas a executáveis de terceiros para apresentar PDF.

Podemos ainda definir quais páginas queremos recuperar, para isso utilizamos o parâmetro -dFirstPage para determinar a página inicial e o -dLastPage para determinar a página final. Caso desejemos recuperar apenas a página 5 e 6 de um documento, por exemplo, utilizaremos a seguinte linha de comando:

gswin32 -dSAFER -dBATCH -dFirstPage=4 -dLastPage=4 <caminho de um arquivo pdf> 

Além de abrir um PDF, podemos gerar um arquivo a partir de um segundo, seja ele de mesma ou de outra extensão, para isso utilizando o comando:

gswin32 -dSAFER -dBATCH -dNOPAUSE -sDEVICE=jpeg -sOutputFile=figure.jpg <meuArquivo.PDF> 

Esse comando utiliza como device de output o device jpeg e gera um arquivo JPG com nome figure.jpg no mesmo diretório do GhostScript a partir do meuArquivo.PDF. Nessa linha utilizamos três novos comandos: -dNOPAUSE, -sDEVICE e -sOutputFile. Vamos entender esses parâmetros: o -dNOPAUSE informa ao GhostScript que no processo não existirá interação com o usuário, ou seja, em casos onde seja preciso pressionar o Enter esse comando removerá essa necessidade. O -sDEVICE serve para definirmos qual o device ("impressora virtual") será utilizado. Esse device é o que define qual a saída ou como será tratado esse documento, nesse caso utilizamos como device uma saída JPEG. Por último temos o parâmetro -sOutputFile, onde informamos o caminho de saída do documento que será gerado a partir do documento original que se encontra informado na frente do arquivo de output. Como não informamos um caminho completo para esse documento, ele será gerado dentro da pasta bin do GhostScrip, juntamente do seu executável.

Podemos utilizar essa mesma técnica para gerar um arquivo TXT com o texto do PDF, para isso basta que como device de output utilizemos o device txtwrite:

gswin32 -dSAFER -dBATCH -dNOPAUSE -sDEVICE=txtwrite -sOutputFile=file.txt MeuPDF.PDF 

Com apenas uma linha de comando temos a capacidade de extrair os dados de um PDF para um arquivo de texto e trabalhamos sobre esse arquivo como preferirmos. Uma aplicação prática para isso seria a extração do texto do PDF e armazenamento dessa informação em banco de dados para permitir ao cliente efetuar consultas posteriormente. Nesse mesmo cenário podemos também aplicar validações sobre o conteúdo do PDF antes de inseri-lo no sistema, por exemplo. A aplicação de novas funcionalidades depende apenas das necessidades do projeto.

Para sabermos quais devices estão disponíveis, podemos digitar no prompt a seguinte linha de comando:

gswin32 -h ou gswin32 -?

Além de apresentar um help, será também exibido logo abaixo um trecho Available devices, onde estarão descritos todos os devices disponíveis. Veremos que existem devices para trabalharmos com inúmeros tipos de arquivos (JPEG, BMP, PNG, TIFF e PDF).

Para obtermos suporte sobre suas funções podemos utilizar a seguinte linha de comando:

gswin32 -h ou  gswin32 -?

Para solucionar o problema com fontes descrito anteriormente podemos utilizar o parâmetro -sFONTPATH= para informar o caminho da fonte que não foi reconhecida.

Caso o arquivo com o qual estejamos lidando esteja com alguma senha aplicada, podemos utilizar do parâmetro para sPDFPassword para definir a senha do usuário a ser utilizada na decodificação do PDF criptografado.

Comprimindo arquivos

Para geramos arquivos PDF menores com o GhostScript é muito simples, basta criarmos um arquivo PDF a partir de um outro já existente utilizando o device pdfwrite:

gs -dSAFER -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sOutputFile=PDFComprimido.pdf PDFOriginal.pdf

Essa linha de código pega o arquivo PDFOriginal.pdf e gera um novo com nome de PDFComprimido.pdf na saída a partir do device pdfwrite. O GhostScript já tem uma inteligência capaz de reduzir sem a inserção de nenhum novo parâmetro, porém podemos melhorar essa compressão com a inserção de alguns argumentos para tratamentos específicos.

A compressão pelo GhostScript é feita sempre que possível, em casos em que o arquivo original já se encontrar comprimido, não será possível efetuar a compressão.

Para que possamos observar o processo compressão na prática e avaliar os resultados, vamos trabalhar com os três arquivos que são criados juntamente com a instalação do GhostScript: annots.pdf, text_graph_image_cmyk_rgb.pdf e text_graphic_image.pdf que se encontram na subpasta examples no diretório de instalação. E para facilitar vamos renomear os arquivos para File1.pdf, File2.pdf e File3.pdf respectivamente, além copiá-los para a pasta bin, de forma que que possamos trabalhar sem a necessidade de informar o caminho completo dos arquivos, reduzindo assim nossas linhas de comando.

Agora vamos gerar um arquivo comprimido para cada um desses PDFs, bastando para isso executar os comandos da Listagem 1 no Prompt de Comandos.

Listagem 1. Comandos para gerar novos PDF a partir do GhostScript.

     gswin32 -dSAFER -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sOutputFile=file1_compressed.pdf file1.pdf
     gswin32 -dSAFER -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sOutputFile=file2_compressed.pdf file2.pdf
     gswin32 -dSAFER -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sOutputFile=file3_compressed.pdf file3.pdf
     

Para fins de comparação utilizaremos a Tabela 1, onde podemos observar o tamanho original de cada um dos arquivos e o tamanho final dos arquivos gerados a partir de sua compressão.

Arquivo

Tamanho Original

Tamanho pos Compressão

Taxa de compressão

File1.pdf

385 KB

352 KB

8%

File2.pdf

342 KB

117 KB

65%

File3.pdf

131 KB

57 KB

56%

Tabela 3 1. Comparação dos tamanhos dos arquivos após a compressão.

Podemos ver que não foi necessário nenhum parâmetro adicional, apenas geramos um novo arquivo utilizando o device pdfwrite, com isso já obtivemos uma boa compressão. Para melhorar essa taxa vamos gerar novos arquivos utilizando os comandos da Listagem 2 no prompt.

Listagem 2. Comandos para gerar e mehorar a compressão dos PDF a partir do GhostScript

     gswin32 -dSAFER -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -dPDFSETTINGS=/ebook -sOutputFile=file1_compressed.pdf file1.pdf
     gswin32 -dSAFER -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -dPDFSETTINGS=/ebook -sOutputFile=file2_compressed.pdf file2.pdf
     gswin32 -dSAFER -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -dPDFSETTINGS=/ebook -sOutputFile=file3_compressed.pdf file3.pdf
     

Adicionamos apenas o parâmetro -dPDFSETTINGS e setamos seu valor para /ebook, com isso o GhostScript gera uma melhoria na taxa de compressão. Na Tabela 2 podemos analisar o resultado em comparação à compressão sem o parâmetro.

Arquivo

Tamanho Original

Compressão com dPDFSETTINGS

Taxa de compressão

File1.pdf

385 KB

342 KB

11%

File2.pdf

342 KB

85 KB

75%

File3.pdf

131 KB

56 KB

57%

Tabela 3 2. Comparação de compressão com configuração -dPDFSETTINGS.

Após a compressão com a utilização do parâmetro -dPDFSETTINGS tivemos uma redução ainda maior no tamanho dos arquivos finais, e mesmo assim não houve perda de qualidade. O arquivo File3.pdf teve a menor diferença na redução do tamanho, diferença que se deve ao nível de compressão já aplicado sobre esse arquivo e o GhostScript não conseguir aplicar ainda mais. É possível forçar a compressão sobre os arquivos, mais isso poderá acarretar em perda de qualidade.

Parâmetros do GhostScript

Aqui contemplaremos apenas alguns dos parâmetros que são mais importantes para este artigo, visto que o GhostScript possui um número grande de argumentos possíveis. Mais informações sobre os demais parâmetros podem ser obtidas na página GhostScript (seção Links). A seguir temos a lista dos parâmetros a serem trabalhados nesse artigo:

  • -sDEVICE=<device_out_put>: Parâmetro utilizado para definir qual dispositivo de saída será utilizado. Em nosso caso utilizaremos o device pdfwrite que é nossa “impressora virtual” para gerar PDF para PS, PS para PDF e PDF para PDF. Essa impressora não é apresentada no painel de controle e em nenhum lugar a não ser no próprio GhostScript, sendo um recurso nativo dele.
  • -dCompressFonts=<True_Ou_False>: Define se iremos utilizar compressão de fontes ou não, recebendo True ou False. Testes revelaram que o ganho com esse parâmetro é pouco, mais ainda assim seu uso é válido para arquivos com muito texto.
  • -dPDFSETTINGS=/<tipo>: Parâmetro utilizado para definir um tipo de configuração de saída padrão, podendo ser ela para visualização em vídeo ou impressão. Essa configuração determina muitos padrões automáticos de compressão, garantindo ganhos visíveis, porém ao definir esse parâmetro como Screen as perdas de qualidade nas imagens são visualmente percebidas, por isso aqui utilizaremos o valor ebook. Esse parâmetro cria arquivos maiores que com Screen porém com perda visual muito menor ou quase zero.
  • -dDetectDuplicateImages=<True_Ou_False>: Aqui definimos se iremos utilizar a detectação de imagens duplicadas, com isso gravando apenas uma referência no lugar das demais imagens duplicadas. Em caso de se encontrar imagens duplicadas a diferença no tamanho dos arquivos é considerável.
  • -dQFactor=<fator>: Para esse parâmetro deveremos utilizar um valor entre 0.1 e 1.0 como fator para compressão, sendo 1.0 sem compressão e 0.1 o maior nível de compressão. Testes com esse parâmetro indicaram que valores até 0.2 em arquivos JPG em tons coloridos quase não geram perda visual na qualidade e a compressão chegou a mais de 90% na maioria dos casos. Por exemplo, arquivos de 15MB tiveram seu tamanho reduzido para 130KB. Porém, em arquivos com tons de cinza ou preto e branco existe uma perda visual muito significativa, em alguns casos impedindo a identificação da imagem.
  • -dJPEGQ=<valor>: Para esse parâmetro devemos determinar um valor na faixa de 0 a 100, sendo que quanto menor for o valor, menor será a qualidade das imagens JPEG. Apesar das configurações serem “exclusivas” para JPEG, as mesmas se aplicam a outros tipos de imagens, como PNG, GIF, TIF e BMP em alguns casos. Assim como o -dQFactor esse parâmetro causa problemas com imagens com tons de cinza e preto e branco.
  • -dDownsample<Tipo>Images=<True_Ou_False>: Parâmetro utilizado para definir se será aplicada redução de qualidade (True ou False) para o tipo de imagem (Color, Gray, Mono). Iremos utilizar um parâmetro para cada tipo com valor True, ou seja, iremos aplicar compressão e baixa resolução para cada tipo de imagem.
  • -d<tipo>ImageResolution=<Taxa>: Nesse parâmetro informamos qual será a taxa de resolução aplicada para cada tipo de imagem (Color, Gray, Mono), podendo ser desde 0 até 3000. Com esse parâmetro definiremos para cada tipo de imagem se ocorrerá uma redução ou “melhora” na imagem. Quanto maior o valor, maior será sua resolução. Em testes foi visto que com perdas visuais zero o valor ideal é de 200, pois acima disso o arquivo tem seu tamanho aumentado e não é percebida diferença visual, principalmente para arquivos de impressão. Com menos de 200 temos melhores resultados na compressão, porém temos perdas perceptíveis, principalmente em casos de arquivos em tons de cinza e monocromático, por isso no tipo deve-se ter uma configuração para cada tipo de imagem.
  • -sOutputFile=<Destino_do_Arquivo>: Parâmetro utilizado para definir qual o local de saída do arquivo e seu nome. O caminho dos arquivos não deve conter espaços ou caracteres especiais, caso contrário o GhostScript pode gerar um erro por não conseguir identificar o local de destino.

Integração entre Delphi e GhostScript

Antes de iniciar nossa integração, vamos entender como ela é feita. Toda a comunicação do Delphi com o GhostScript é feita através de uma DLL, o que nos permite interagir de forma padronizada com sua API, visto que a DLL deve ser escrita sobre um padrão e todas as linguagens têm a capacidade de interpretar seus recursos. Porém, para que esses recursos fiquem disponíveis, devemos importa-los para nossa aplicação. Para isso devemos escrever uma unit que terá declarados os métodos desejados e farão referência ao método na DLL. Esta serve de "ponte" entre o resto da nossa aplicação e a biblioteca do GhostScript. Essa técnica não é exclusiva do GhostScript e serve para qualquer comunicação com DLL. As DLLs podem ser carregadas de forma dinâmica ou estática. Dinamicamente ela é carregada sempre que temos a necessidade de utilizá-la, e em caso dela não estar disponível e a função não ser executada, nenhum erro será apresentado. Já ao carregar a DLL de forma estática, ela vira uma dependência para o executável e é carregada na sua abertura, independente de se será utilizada ou não, e caso ela não esteja disponível um erro será apresentado e aplicação não irá abrir.

Agora que temos um breve conhecimento do processo de integração, podemos iniciar nossa aplicação. Para isso vamos criar um novo projeto com o nome DelphiComGhostScript.dproj. Adicionaremos uma unit que irá importar a DLL do GhostScript, mas como não vamos utilizar toda sua biblioteca vamos importar apenas parte dela. Como podemos observar na Listagem 3, declaramos e implementamos quatro métodos externos com chamadas à DLL do GhostScript, que deverá ser distribuída juntamente ao nosso EXE. Os métodos que iremos importar são os métodos para instanciar o GhostScript (gsapi_new_instance), o método responsável por liberar essa instância (gsapi_exit e gsapi_delete_instance) e o método que irá servir como nosso Prompt de Comandos responsável por passar os parâmetros ao GhostScript (gsapi_init_with_args) e retornar um código do processamento.

Listagem 3. Unit para importar biblioteca da dll do GhostScript

   unit uGhostScript;
   
     interface
     
     type
      PPAnsiChar = array of PAnsiChar;
     {$NODEFINE PPAnsiChar}
     
            function gsapi_new_instance(var pinstance: Pointer; caller_handle: Pointer): Integer; stdcall;
            {$EXTERNALSYM gsapi_new_instance}
    
            procedure gsapi_delete_instance(instance: Pointer); stdcall;
            {$EXTERNALSYM gsapi_delete_instance}
    
            function gsapi_init_with_args(instance: Pointer; argc: Integer; argv: PPAnsiChar): Integer; stdcall;
            {$EXTERNALSYM gsapi_init_with_args}
     
            function gsapi_exit(instance: Pointer): Integer; stdcall;
            {$EXTERNALSYM gsapi_exit}
     
     Const gsdll32 = 'gsdll32.dll';
     
     implementation
     
     function gsapi_new_instance; external gsdll32 name 'gsapi_new_instance';
     procedure gsapi_delete_instance; external gsdll32 name 'gsapi_delete_instance';
     function gsapi_init_with_args; external gsdll32 name 'gsapi_init_with_args';
     function gsapi_exit; external gsdll32 name 'gsapi_exit';
     
     end.

O GhostScript fornece outros métodos para importação, vamos dar uma olhada nos principais e quais tarefas são designadas a eles:

  • gsapi_new_instance(): Cria uma nova instância do GhostScript. Essa instância é passada para a maioria das outras funções da API. O handle será fornecido para funções de retorno de chamada. O GhostScript permite apenas uma instância de cada vez, qualquer tentativa para criar uma nova resulta em um erro. Se o GhostScript foi compilado com a diretiva GS_THREADSAFE, várias instâncias simultâneas são permitidas;
  • gsapi_delete_instance(): Destrói uma instância do GhostScript. Antes de chamar este método o GhostScript deve ter sido terminado utilizando o método gsapi_exit();
  • gsapi_set_stdio(): Se você usar esse método todo stdio será redirecionado para as funções de retorno de chamada que você forneceu. Isto seria usado em um ambiente de interface gráfica do usuário onde stdio não está disponível, ou onde você deseja processar entradas e saídas do GhostScript;
  • gsapi_set_display_callback(): Define a estrutura de retorno de chamada para o visor do dispositivo. Se o visor do dispositivo é utilizado, então deve ser chamado após gsapi_new_instance() e antes gsapi_init_with_args();
  • gsapi_set_arg_encoding(): Define a codificação utilizada para a interpretação de todos os argumentos;
  • gsapi_init_with_args(): Inicializa o interpretador e executa com os parâmetros passados. Esse método "simula” o Prompt de Comandos;
  • gsapi_exit(): Sai do interpretador. Este método deve ser chamado para liberar o interpretador se gsapi_init_with_args() foi chamado, e pouco antes de gsapi_delete_instance().

Após importamos a biblioteca necessária, nosso próximo passo deve ser criar uma classe que será responsável por efetuar as chamadas e passar os parâmetros para o GhostScript. Nossa classe será nomeada como TCompressaoPDF (Listagem 4), criada em uma unit separada que deve ser nomeada como uCompressaoPDF, onde deve ser declarada em sua seção uses a unit uGhostScript, que é unit que criamos com as declarações dos métodos importados do GhostScript.

Listagem 4. Prototype da classe TCompressaoPDF

      TCompressaoPDF = Class
        private
          fOutput          : String ;
          fInput           : String ;
          fResolutionColor : Integer;
         fResolutionGray  : Integer;
        fResolutionMono  : Integer;
          fDetectDuplicateImages : String;
        public
          constructor Create;
    
          Function Executar : Boolean;
        published
          Property Output  : String  read fOutput  write fOutput    ;
          Property Input   : String  read fInput   write fInput   ;
          Property ResolutionColor : Integer read fResolutionColor write   fResolutionColor;
          Property ResolutionGray  : Integer read fResolutionGray  write   fResolutionGray ;
          Property ResolutionMono  : Integer read fResolutionMono  write  fResolutionMono ;
          Property DetectDuplicateImages  : String read fDetectDuplicateImages   write fDetectDuplicateImages ;
      End;

Essa classe é composta de seis propriedades, sendo elas o caminho e nome do documento a ser comprimido (Input), o caminho e nome do arquivo a ser gerado (Output), se deve detectar imagens duplicadas (DetectDuplicateImages) e três parâmetros referentes à resolução de cada tipo de imagem: colorida, tons de cinza e monocromática, respectivamente (ResolutionColor, ResolutionGray e ResolutionMono). Nossa classe também contém dois métodos, sendo o primeiro o Create (construtor, que pode ser visto na Listagem 5), onde iremos inserir os valores padrões para nossas propriedades e o segundo o método Executar, onde será implementada a rotina para compressão do PDF, como vemos na Listagem 6.

Listagem 5. Construtor.

   constructor TCompressaoPDF.Create;
   begin
     inherited;
     ResolutionColor := 200;
     ResolutionGray  := 200;
      ResolutionMono  := 200;
      DetectDuplicateImages := 'true';
     end;
     
Listagem 6. Função de execução da compressão.

     function TCompressaoPDF.Executar: Boolean;
     const
       Nparams = 16;
     var
       argv    : PPChar ;
      code    : integer;
     instance: Pointer;
     begin
       result := False;
    
       setlength(argv, Nparams);
       argv[0] := '-dSAFER';
       argv[1] := '-dBATCH';
       argv[2] := '-dNOPAUSE';
       argv[3] := '-sDEVICE=pdfwrite';
       argv[4] := '-dCompressFonts=true';
       argv[5] := '-dPDFSETTINGS=/ebook';
       argv[6] := PAnsiChar(AnsiString('-dDetectDuplicateImages='+ DetectDuplicateImages));
       argv[7] := '-dDownsampleColorImages=true \';
       argv[8] := '-dDownsampleGrayImages=true \';
       argv[9] := '-dDownsampleMonoImages=true \';
       argv[10]:= PAnsiChar(AnsiString('-dColorImageResolution=' +  IntToStr(ResolutionColor) + ' \'));
       argv[11]:= PAnsiChar(AnsiString('-dGrayImageResolution='  +  IntToStr(ResolutionGray)  + ' \'));
       argv[12]:= PAnsiChar(AnsiString('-dMonoImageResolution='  +  IntToStr(ResolutionMono)  + ' \'));
       argv[13]:= PAnsiChar(AnsiString('-sOutputFile=' + Output));
       argv[14]:= '-f';
       argv[15]:= PAnsiChar(AnsiString(Input));
    
       try
         code := gsapi_new_instance(@instance, nil);
         if code <> 0 then
           raise Exception.Create('Erro ao instanciar. Código:'+ IntToStr(code));
    
       code := gsapi_init_with_args(instance, Nparams, argv);
       if code < 0 then
         raise Exception.Create('Erro ao converter. Código:+  IntToStr(code));
  
       result := True;
     finally
       gsapi_exit(instance);
       gsapi_delete_instance(instance);
     end;
   end; 

No método Executar foi criada uma constante chamada de NParams, que deve ter o valor refletido na quantidade de parâmetros a ser passada ao GhostScript. Esses parâmetros serão inseridos na nossa variável argv, sendo ela um array de PChar. Foi declarada também uma variável para armazenar os códigos retornados pelo GhostScript e uma variável que representará a instância do GhostScript.

Após alterar o resultado para False, alteramos o tamanho do nosso array argv com o valor da constante NParams. Em seguida, para cada registro do array passamos um parâmetro e seu respectivo valor, concatenando-os com os valores das propriedades da classe.

Depois de passar todos os parâmetros, dentro de um bloco try é feita a chamada ao método de instanciar o GhostScript, que foi importado na uGhostScript. Para esse método passamos no primeiro parâmetro a variável de instância. Caso não ocorra erro ao tentar instanciar, a variável passada receberá a referência da instancia criada pelo GhostScript. Essa função retorna um código, que caso seja diferente de zero, significa que ocorreu algum erro ao tentar levantar essa instância, caso contrário a instância foi criada corretamente. Esse código de erro é passado para a variável code. Após a tentativa de instanciar o GhostScript, efetuamos um teste verificando se o valor de code é diferente de zero, caso esse valor não seja igual a zero, lançamos uma exceção "Erro ao instanciar" com o código do erro. Se não houver erro ao instanciar o GhostScript, é então chamado o método gsapi_init_with_args da unit uGhostScript, para o qual são passados três parâmetros: a instância que será utilizada, a quantidade de parâmetros e a lista de parâmetros. No primeiro argumento passamos a variável que recebeu a instância do GhostScript, no número de parâmetros passamos a constante NParams e a para lista de parâmetros passamos o array de parâmetros o argv. O método gsapi_init_with_args, assim como o gsapi_new_instance, também retorna um código que é armazenado na variável code. Caso esse valor seja menor que zero, significa que ocorreu algum problema na geração do PDF, por isso efetuamos essa validação em caso de erro lançamos uma exceção "Erro ao converter" também com o código do erro. Caso não ocorra nenhum erro na chamada do método gsapi_init_with_args, é gerado o novo arquivo PDF conforme especificado pelos parâmetros no diretório de saída. Com isso é dado um result true e no finally chamamos os métodos responsáveis por destruir a instância.

Para concluirmos, criaremos uma interface para instanciar e passar os valores desejados para um objeto do tipo TCompressaoPDF. A unit1 do projeto deve ser renomeada para uMain e o formulário renomeado para frmMain. O formulário deve ficar parecido com o da Figura 1, onde temos dois edits, um referente ao documento a ser comprimido e o outro ao local e nome do documento de saída. Para testar os níveis de compressão utilizaremos um CheckBox para determinar se iremos aplicar compressão sobre imagens duplicadas e um TrackBar com valor entre 0 a 300 para definirmos a resolução das imagens no arquivo. Deve ser declarada na seção uses a unit uCompressaoPDF. Nessa unit será implementado apenas um método que será no evento OnClick do botão Executar, cujo código pode ser visto na Listagem 7.

Manipulando PDF com GhostScript
Figura 1. Formulário principal do sistema.
Listagem 7. Método do evento onclick do executar.

     procedure TfrmMain.BtnExecutarClick(Sender: TObject);
     Var CompressaoPDF : TCompressaoPDF;
     begin
       CompressaoPDF := TCompressaoPDF.Create;
       try
        CompressaoPDF.Input           := edtInput.Text;
       CompressaoPDF.Output          := edtOutput.Text;
         CompressaoPDF.ResolutionColor := trckbrResolucao.Position;
         CompressaoPDF.ResolutionGray  := trckbrResolucao.Position;
         CompressaoPDF.ResolutionMono  := trckbrResolucao.Position;
     
         if chkDetectDuplicateImages.Checked then
           CompressaoPDF.DetectDuplicateImages := 'true'
         else
           CompressaoPDF.DetectDuplicateImages := 'false';
     
         If CompressaoPDF.Executar Then
           ShowMessage('Arquivo gerado com sucesso!')
         else
           ShowMessage('Falha ao gerar arquivo!');
     
       finally
         FreeAndNil(CompressaoPDF);
       end;
     end;

No evento OnClick do botão Executar foi declarada uma variável do tipo TCompressaoPDF, que é instanciado logo abaixo e tem suas propriedades preenchidas com os valores coletados no formulário. Para a propriedade DetectDuplicateImages é efetuada uma validação: se o valor for True então é passado para a propriedade true como string e caso contrário é passado “false”. Com isso concluímos a aplicação de compressão.

Para avaliação dos resultados vamos utilizar um arquivo que não está disponível na pasta do GhostScript, mas estará disponível juntamente ao código fonte deste artigo, que é File4.pdf, um arquivo com 811KB contendo apenas imagens duplicadas, excelente para nossos testes. Ao executar o sistema e gerar um PDF a partir dele, podemos acompanhar os resultados que são listados na Tabela 3. Ao analisar os arquivos de saída veremos nitidamente que naqueles em que a resolução está muito baixa a perda de qualidade é percebida a ponto de em alguns casos não ser mais possível identificar a imagem. Os arquivos com valor de resolução até 100 ainda são arquivos de qualidade considerável, tendo uma boa nitidez e alta compressão. A partir dessa tabela podemos concluir que vale a pena utilizar a configuração para detectar imagens duplicadas, visto que o ganho na compressão é de mais que a metade em alguns casos, porém isso vai depender da quantidade de imagens repetidas no arquivo.

Detect Duplicate Images

Resolução

Tamanho do arquivo gerado

Sim

300

445 KB

Não

300

885 KB

Sim

200

445 KB

Não

200

885 KB

Sim

150

235 KB

Não

150

493 KB

Sim

100

136 KB

Não

100

267 KB

Sim

50

62 KB

Não

50

109 KB

Sim

25

38 KB

Não

25

56 KB

Tabela 3. Comparação de compressão pelo aplicativo.

O GhostScript não se resume às funcionalidades que foram exploradas nesse artigo, aqui utilizamos apenas a parte do PostScript, que é a uma API responsável por trabalhar com PDF. O PostScript ainda tem uma infinidade de recursos e podemos programar e adicionar novas funcionalidades. Se analisarmos sua documentação, veremos que o número de parâmetros é enorme e para cada combinação desses argumentos temos novos resultados.

São diversas as possibilidades que podem ser exploradas utilizando a API do GhostScript. Por exemplo, ao extrair o texto de um arquivo PDF podemos efetuar diversos procedimentos sobre os dados obtidos, desde simples validações para armazenamento em bases locais até mineração para extração de informações estratégicas baseadas em arquivos gerados em grande volume.

Para o cliente, a redução do espaço utilizado em disco reflete em economia com manutenção e aquisição de novos equipamentos, e em cenários onde esses arquivos são armazenados em uma base externa (com backup na nuvem, por exemplo), a diferença pode ser notada já nos primeiros períodos, com a redução do tráfego de dados e espaço alocado.

Quando os arquivos forem enviados aos clientes, a redução no tamanho tende a garantir uma experiência de uso mais fluída, uma vez que o download e abertura do arquivo se tornarão mais rápidos. Em cenários onde o cliente precisa, por exemplo, fazer download desses arquivos através de uma conexão 3G, de baixa velocidade, e com limite de tráfego reduzido, essa necessidade se torna ainda mais crítica.

Com base nas funcionalidades oferecidas pelo GhostScript, pode-se atender diversas demandas, bastando utilizar os parâmetros adequados. No site oficial da ferramenta, cujo endereço pode ser visto na seção Links, podem ser encontrados maiores detalhes sobre licença, documentação, bugs conhecidos, além de canais para interação com a comunidade que utiliza e contribui com o desenvolvimento da ferramenta.