Uma equipa de processamento de sinistros tinha trinta anos de ficheiros em papel a passar por um digitalizador alimentado por folhas. O digitalizador gerava um JPEG por página para uma pasta, com nomes como 0001.jpg, 0002.jpg e assim sucessivamente. O que o arquivo realmente necessitava era de um único PDF por ficheiro de caso, com as páginas ordenadas, para que um revisor pudesse abrir um único documento em vez de clicar em centenas de miniaturas de imagens. Esse último passo, transformar uma pilha numerada de imagens digitalizadas num único PDF ordenado, é o trabalho aqui abordado
O PDFium Component trata disso diretamente. Além da renderização e da extração de texto, o componente pode construir um PDF do zero: criar um documento vazio, adicionar uma página em branco dimensionada como desejar, colocar uma imagem nessa página em coordenadas do espaço do utilizador e, em seguida, gravar. Todo o processo reside no componente TPdf, pelo que um conversor em lote se resume a um ciclo sobre nomes de ficheiros mais algumas chamadas de métodos
A estrutura da conversão
Três coisas têm de acontecer para cada imagem digitalizada. Decide o tamanho da página, coloca a imagem dentro da página deixando uma margem e avança para a página seguinte. O PDFium Component fornece um método para cada uma destas ações: o AddPage cria uma página em branco com um determinado tamanho, o AddImage (ou o AddPicture se já possuir um TPicture) desenha o bitmap na página atual, e o PageNumber indica ao componente quais as páginas que as chamadas de desenho seguintes têm como alvo
O único detalhe que baralha as pessoas é o sistema de coordenadas. O espaço do utilizador do PDF coloca a origem no canto inferior esquerdo da página, com o Y a crescer para cima, o oposto das coordenadas do ecrã que os programadores Delphi utilizam por reflexo. O X, Y que passa ao AddImage é o canto inferior esquerdo do retângulo da imagem, e o Width, Height representa o tamanho de colocação em pontos, não o tamanho em píxeis do ficheiro de origem. Se errar nesta lógica, as suas imagens digitalizadas irão parar fora da página ou de pernas para o ar em relação ao esperado
Criar o documento e uma página por imagem digitalizada
Comece com um documento vazio. O CreateDocument aloca um novo PDF e deixa o componente ativo, pelo que não existe um passo de abertura separado. A partir daí, percorre a lista de ficheiros digitalizados e, para cada um deles, adiciona uma página, torna-a ativa e coloca a imagem. As dimensões da página aqui são as do formato A4 em pontos (595 × 842 em modo vertical), o tamanho de folha padrão para correspondência arquivada
procedure TArchiveForm.ScansToPdf(const Files: TStrings; const OutputPath: string);
const
PageW = 595.0; // largura A4 em pontos
PageH = 842.0; // altura A4 em pontos
Margin = 36.0; // margem de meia polegada à volta de cada digitalização
var
I: Integer;
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.CreateDocument; // novo, vazio, já ativo
for I := 0 to Files.Count - 1 do
begin
Pdf.AddPage(I + 1, PageW, PageH); // índice de página baseado em 1
Pdf.PageNumber := I + 1; // torna a nova página atual
PlaceScan(Pdf, Files[I], PageW, PageH, Margin);
end;
Pdf.SaveAs(OutputPath);
finally
Pdf.Free;
end;
end;
Cada iteração cria uma página e define imediatamente o PageNumber para a mesma. Essa segunda linha é crucial: o AddPage insere a página, mas os métodos de desenho atuam na página que estiver ativa, pelo que definir o PageNumber é o que direciona o AddImage para a página que acabou de criar. Ignore este passo e as suas imagens acumular-se-ão na página que estivesse carregada anteriormente
Uma suposição oculta-se nesse ciclo: a ordenação dos Files. Um digitalizador nomeia as páginas de 0001.jpg a 0100.jpg, mas a enumeração de uma pasta nem sempre as devolve ordenadas. No momento em que encontra o page9.jpg junto ao page10.jpg, uma ordenação simples de strings coloca a página 10 antes da página 9. Ordene a lista explicitamente antes do ciclo e prefira nomes com preenchimento de zeros no momento da digitalização para que a ordem lexical corresponda à ordem das páginas. A sequência de páginas é a primeira coisa que um revisor nota e é o erro mais barato de evitar
Colocar uma imagem digitalizada e manter a sua proporção
Uma digitalização raramente tem a mesma forma que a página. Se a esticar para preencher a folha, distorce o texto; se a colocar com o tamanho de píxel original, ela ultrapassa os limites. A solução é dimensionar pela menor das duas proporções (ajuste de largura ou ajuste de altura) e centrar o espaço restante. Como a origem está no canto inferior esquerdo, centrar significa dividir o espaço restante uniformemente e adicioná-lo tanto ao X como ao Y
procedure TArchiveForm.PlaceScan(Pdf: TPdf; const FileName: string;
PageW, PageH, Margin: Double);
var
Pic: TPicture;
AvailW, AvailH, Scale, DrawW, DrawH, X, Y: Double;
begin
Pic := TPicture.Create;
try
Pic.LoadFromFile(FileName); // BMP, JPG, PNG, etc. através das unidades gráficas da VCL
AvailW := PageW - 2 * Margin;
AvailH := PageH - 2 * Margin;
// Ajustar dentro das margens sem distorcer a digitalização.
Scale := Min(AvailW / Pic.Width, AvailH / Pic.Height);
DrawW := Pic.Width * Scale;
DrawH := Pic.Height * Scale;
// Centrar: o espaço restante dividido uniformemente. O Y é medido a partir do fundo da página.
X := (PageW - DrawW) / 2;
Y := (PageH - DrawH) / 2;
Pdf.AddImage(FileName, X, Y, DrawW, DrawH);
finally
Pic.Free;
end;
end;
Isto carrega o ficheiro uma vez para ler as suas dimensões em píxeis, calcula uma escala uniforme e passa o retângulo de colocação ao AddImage. O AddImage aceita diretamente um caminho de ficheiro e encaminha-o através do mesmo processo de imagem que o AddPicture, pelo que qualquer formato que as unidades gráficas da VCL reconheçam funciona sem tratamento especial. Se já tiver a imagem descodificada num TPicture a partir de um painel de pré-visualização, chame AddPicture(Pic, X, Y, DrawW, DrawH) com o mesmo retângulo e evite uma segunda leitura do ficheiro
Evitar a descodificação em imagens JPEG
Os digitalizadores emitem quase sempre ficheiros JPEG. Carregar um JPEG num TPicture descodifica-o para um bitmap, e depois o PDFium volta a codificá-lo ao gravar (dois processos com perdas de qualidade desnecessários). O método AddJpegImage incorpora os bytes comprimidos originais diretamente na página a partir de um fluxo (stream), o que é mais rápido e visualmente mais limpo para um processamento em lote de grande volume
var
Stream: TFileStream;
begin
// ... depois de AddPage + PageNumber para a página atual ...
Stream := TFileStream.Create(FileName, fmOpenRead);
try
// Incorpora os bytes JPEG tal como estão; sem ciclo de descodificação/re-codificação.
Pdf.AddJpegImage(Stream, X, Y, DrawW, DrawH);
finally
Stream.Free;
end;
end;
Continua a calcular o X, Y, DrawW e DrawH da mesma forma, pois necessita das dimensões em píxeis para redimensionar. Leia-as a partir do ficheiro ou através de uma rápida análise do cabeçalho, e depois entregue o fluxo direto ao AddJpegImage. Para digitalizações em PNG ou TIFF, o caminho do AddImage é o correto; reserve o atalho do JPEG apenas para o formato a que realmente se aplica
Identificar cada página
As imagens digitalizadas em arquivo são mais fáceis de auditar quando cada página contém o nome do seu ficheiro de origem. O AddText desenha uma string numa coordenada do espaço do utilizador, pelo que a legenda fica posicionada logo abaixo da imagem. Lembre-se do eixo Y invertido: para colocar uma legenda abaixo da digitalização, subtrai à margem inferior da imagem em vez de somar
// Legenda abaixo da digitalização: o Y decresce em direção ao fundo da página.
Pdf.AddText('File: ' + ExtractFileName(FileName), 'Helvetica', 9,
X, Y - 14, clGray);
Um último ponto sobre a gravação. O SaveAs é uma função que retorna um Boolean, pelo que no código de produção deve verificar o seu resultado em vez de assumir que a gravação foi bem-sucedida; caso contrário, um disco cheio ou um caminho de saída bloqueado falhará silenciosamente. Assim que o ciclo terminar e o ficheiro estiver gravado, terá exatamente o que o arquivo necessitava: um PDF ordenado por ficheiro de caso, com páginas dimensionadas para ajuste, pronto para ser lido em qualquer visualizador
Os mesmos blocos de construção cobrem tarefas semelhantes. Altere a regra de dimensionamento por página e obterá um álbum de fotografias com uma imagem por folha; mantenha o ciclo mas leia a partir de uma origem TIFF multipágina e terá um conversor de arquivo de fax. Se pretender uma visão mais ampla da construção programática de PDFs, consulte o artigo sobre a criação de documentos PDF de raiz com o PDFium Component; para renderizar o resultado de volta ao ecrã mais tarde, consulte a conversão de páginas PDF em imagens JPEG com o PDFium Component
O Componente PDFium Component da loslab.com reúne as APIs de criação de documentos, renderização e texto utilizadas ao longo desta série