Artigo Técnico

Combine Imagens Digitalizadas em um PDF com o Componente PDFium

Uma equipe de processamento de sinistros tinha trinta anos de arquivos em papel passando por um scanner com alimentação de folhas. O scanner cuspiu um JPEG por página em uma pasta, com os nomes 0001.jpg, 0002.jpg e assim por diante. O que o arquivo realmente precisava era de um PDF por arquivo de caso, com as páginas em ordem, para que um revisor pudesse abrir um único documento em vez de clicar em cem miniaturas de imagens. Essa última etapa, transformando uma pilha numerada de digitalizações em um único PDF ordenado, é o trabalho aqui

O Componente PDFium lida com isso diretamente. Além da renderização e da extração de texto, o componente pode criar um PDF do zero: criar um documento vazio, adicionar uma página em branco com o tamanho que você desejar, soltar uma imagem nessa página nas coordenadas do espaço do usuário e depois salvar. Todo o pipeline reside no componente TPdf, portanto, um conversor em lote é um loop sobre nomes de arquivo além de um punhado de chamadas

O formato da conversão

Três coisas precisam acontecer para cada digitalização. Você decide o tamanho da página, você coloca a imagem dentro da página deixando uma margem, e você avança para a próxima página. O Componente PDFium lhe dá um método para cada: AddPage cria uma página em branco com um determinado tamanho, AddImage (ou AddPicture se você já tiver um TPicture) desenha o bitmap na página atual, e PageNumber diz ao componente qual página as chamadas de desenho subsequentes têm como alvo

A única coisa que confunde as pessoas é o sistema de coordenadas. O espaço do usuário do PDF coloca a origem no canto inferior esquerdo da página, com o Y aumentando para cima, o oposto das coordenadas de tela que os desenvolvedores Delphi buscam por reflexo. O X, Y que você passa para AddImage é o canto inferior esquerdo do retângulo da imagem, e Width, Height são o tamanho da colocação em pontos, não o tamanho em pixels do arquivo de origem. Faça o inverso e suas digitalizações cairão fora da página ou de cabeça para baixo em relação a onde você esperava que elas estivessem

Criando o documento e uma página por digitalização

Comece com um documento vazio. CreateDocument aloca um PDF novo e deixa o componente ativo, portanto, não há uma etapa de abertura separada. A partir daí você percorre a lista de arquivos digitalizados e, para cada um, você adiciona uma página, a torna atual e coloca a imagem. As dimensões da página aqui são A4 em pontos (595 × 842 retrato), o tamanho de folha padrão para correspondência arquivada

procedure TArchiveForm.ScansToPdf(const Files: TStrings; const OutputPath: string);
const
  PageW = 595.0;   // A4 width in points
  PageH = 842.0;   // A4 height in points
  Margin = 36.0;   // half-inch border around each scan
var
  I: Integer;
  Pdf: TPdf;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.CreateDocument;                       // new, empty, already active
    for I := 0 to Files.Count - 1 do
    begin
      Pdf.AddPage(I + 1, PageW, PageH);       // 1-based page index
      Pdf.PageNumber := I + 1;                // make the new page current
      PlaceScan(Pdf, Files[I], PageW, PageH, Margin);
    end;
    Pdf.SaveAs(OutputPath);
  finally
    Pdf.Free;
  end;
end;

Cada iteração cria uma página e imediatamente define o PageNumber para ela. Essa segunda linha importa: AddPage insere a página, mas os métodos de desenho atuam na página que estiver atual, então definir o PageNumber é o que direciona AddImage para a página que você acabou de criar. Pule isso e suas imagens vão se empilhar em qualquer página que por acaso estivesse carregada antes

Uma suposição se esconde naquele loop: a ordem de Files. Um scanner nomeia páginas de 0001.jpg a 0100.jpg, mas uma enumeração de diretório nem sempre as retorna classificadas e, no momento em que você encontra a page9.jpg perto da page10.jpg, uma classificação de string simples coloca a página 10 antes da página 9. Classifique a lista explicitamente antes do loop e prefira nomes preenchidos com zeros na hora da digitalização, para que a ordem lexical corresponda à ordem das páginas. A sequência das páginas é a primeira coisa que um revisor percebe e é o erro mais barato de se evitar

Colocando uma digitalização e mantendo a sua proporção

Raramente uma digitalização tem o mesmo formato da página. Se você a esticar para preencher a folha, você distorce o texto; se você a colocar no tamanho total em pixels, ela transborda. A solução é escalar pela menor das duas proporções, adequar à largura ou adequar à altura e centralizar o que sobrar. Como a origem fica no canto inferior esquerdo, a centralização significa dividir o espaço restante uniformemente e adicioná-lo tanto ao X quanto 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. via the VCL graphics units

    AvailW := PageW - 2 * Margin;
    AvailH := PageH - 2 * Margin;

    // Fit inside the margins without distorting the scan.
    Scale := Min(AvailW / Pic.Width, AvailH / Pic.Height);
    DrawW := Pic.Width * Scale;
    DrawH := Pic.Height * Scale;

    // Center: leftover space split evenly. Y measured from the page bottom.
    X := (PageW - DrawW) / 2;
    Y := (PageH - DrawH) / 2;

    Pdf.AddImage(FileName, X, Y, DrawW, DrawH);
  finally
    Pic.Free;
  end;
end;

Isso carrega o arquivo uma vez para ler as suas dimensões em pixels, calcula uma escala uniforme única e passa o retângulo de posicionamento para AddImage. O AddImage aceita um caminho de arquivo diretamente e o encaminha por meio do mesmo pipeline de imagem do AddPicture, portanto, qualquer formato que as unidades gráficas VCL reconheçam funciona sem casos especiais. Se você já tem a imagem decodificada em um TPicture vinda do painel de visualização, chame o AddPicture(Pic, X, Y, DrawW, DrawH) contendo o idêntico arranjo sem efetuar a leiturada e carregar de novo

Pulando a decodificação para digitalizações em JPEG

Scanners quase sempre emitem em formato JPEG. O carregamento do arquivo num local para JPEG em TPicture o irá descodificar num formato bitmap onde a seguir ele os condensa no PDF e salva o formato ali como se perdesse qualidades ao faze-lo as duplicatas não úteis. Mas há um AddJpegImage que pode tratar de trazer o arquivo com todos bytes compactados puros dali pelo streaming direto à sua nova e bela página onde você também não vai demorar mais assim ou errar também se trata lotes volumosos de uma vez só

var
  Stream: TFileStream;
begin
  // ... after AddPage + PageNumber for the current page ...
  Stream := TFileStream.Create(FileName, fmOpenRead);
  try
    // Embeds the JPEG bytes as-is; no decode/re-encode cycle.
    Pdf.AddJpegImage(Stream, X, Y, DrawW, DrawH);
  finally
    Stream.Free;
  end;
end;

Sua matemática segue igual e a resolução também se fará do igual nos seus cálculos para a folha e os resultados em X, Y, DrawW, e DrawH não fogem porque também não dá de você escapar sem as métricas no espaço da largura original pra sua proporção escalar da margem de corte. Para outros PNG no geral a função que passa a frente também ajuda de sua base no de leitura AddImage sem problema onde sua escolha por JPEG faz seu lado valer como no atalho certo

Rotulando cada página

Arquivos passados vão ficar menos ruins a uma vista com seu rótulo pela fonte da qual foi tirado. O atalho AddText deixa sua string de letras colocada ali de acordo com o espaço de onde o dono escolheu, que nas letras em baixo ficarão como legendas na figura onde ela foi tirada no processo. Nunca a margem de seu local fará de subir nos padrões da origem Y pois de cima lá as bases estão ali e para chegar às faturas as marcas diminuem pelo lado não indo em formato como no painel que cresce no avesso:

// Caption below the scan: Y decreases toward the page bottom.
Pdf.AddText('File: ' + ExtractFileName(FileName), 'Helvetica', 9,
  X, Y - 14, clGray);

Só uma a mais pra quem está no de salvar. Sua forma ao uso da SaveAs na folha retorna Boolean portanto nos usos por parte a função em código na checagem vai lhe responder caso tudo vá de correto em vez das crenças do que as falhas nem surgem sem a falta onde seus dados caem se a escrita quebra; sem isso sua falha morre calada no fundo do seu HD em espaço e onde se falhou por caminho. No loop que for para a escrita quando terminada se fará do certo nos locais as bases por lote de PDF inteiro para quem precisava ter do total em uma só via com cada um no seu lado escalado para qualquer programa de leitores

Esses pedaços formam a mesma chave da parte ou bloco ali em si mesmo e não em outro. Você poderá também fazer no de formato na parte inteira os usos se uma por uma ou de lote em uma lista e se precisar trazer no outro com partes em mais dados como nos casos TIFF se virão seus próprios lotes de faturas as quais vão. Em tudo no padrão de você compilar com um código ali num uso da forma por base leia por fim na via onde o artigo dizia do outro local como criando documentos PDF do zero com Componente PDFium; se no resultado quer que surja para a visão ali em um quadro os testes do contrário no qual nós passamos nos JPG pode ver o material dele no convertendo páginas de PDF para imagens JPEG com o Componente PDFium

Da via ali a qual você passa se trata do PDFium Component de nossa empresa pelo qual sua via na entrega ali num só conjunto o modo que os sistemas também leem além disso onde vimos acima e outros