Uma biblioteca Java simples para comparar dois arquivos PDF. Os arquivos são renderizados e comparados pixel por pixel. Não há comparação de texto.
Basta incluí-lo como uma dependência. Verifique a versão mais atual disponível:
< dependencies >
< dependency >
< groupId >de.redsix</ groupId >
< artifactId >pdfcompare</ artifactId >
< version >...</ version > <!-- see current version in the maven central tag above -->
</ dependency >
</ dependencies >
Há uma interface de usuário interativa simples, quando você inicia o arquivo jar sem argumentos adicionais (que inicia a classe de.redsix.pdfcompare.Main). Ele permite que você escolha arquivos para comparar e também marque áreas a serem ignoradas e grave-as em um arquivo ignorado.
Ao lado da IU, você pode fornecer um arquivo esperado e real e parâmetros adicionais por meio de uma CLI. Para obter ajuda para a CLI use a opção -h ou --help-.
usage: java -jar pdfcompare-x.x.x-full.jar [EXPECTED] [ACTUAL]
-h,--help Displays this text and exit
...
Mas o foco do PdfCompare está no uso incorporado como biblioteca.
new PdfComparator ( "expected.pdf" , "actual.pdf" ). compare (). writeTo ( "diffOutput" );
Isso produzirá um PDF de saída que pode incluir marcações para as diferenças encontradas. PdfCompare renderiza uma página do esperado.pdf e a mesma página do actual.pdf em uma imagem bitmap e compara essas duas imagens pixel por pixel. Pixels iguais ficam um pouco desbotados. Pixels diferentes são marcados em vermelho e verde. Verde para pixels que estavam no arquivo esperado.pdf, mas não estão presentes no arquivo real.pdf. Vermelho para pixels que estão presentes no arquivo actual.pdf, mas não estavam no arquivo esperado.pdf. E há marcações na borda do papel em magenta para encontrar áreas que diferem rapidamente. As áreas ignoradas são marcadas com um fundo amarelo. As páginas que eram esperadas, mas não chegaram, são marcadas com uma borda vermelha. As páginas que aparecem, mas não eram esperadas, são marcadas com uma borda verde.
O método compare retorna um CompareResult, que pode ser consultado:
final CompareResult result = new PdfComparator ( "expected.pdf" , "actual.pdf" ). compare ();
if ( result . isNotEqual ()) {
System . out . println ( "Differences found!" );
}
if ( result . isEqual ()) {
System . out . println ( "No Differences found!" );
}
if ( result . hasDifferenceInExclusion ()) {
System . out . println ( "Differences in excluded areas found!" );
}
result . getDifferences (); // returns page areas, where differences were found
Por conveniência, writeTo também retorna o status igual:
boolean isEquals = new PdfComparator ( "expected.pdf" , "actual.pdf" ). compare (). writeTo ( "diffOutput" );
if (! isEquals ) {
System . out . println ( "Differences found!" );
}
O método compare pode ser chamado com nomes de arquivos como Strings, Files, Paths ou InputStreams.
Também é possível definir áreas retangulares que são ignoradas durante a comparação. Para isso, é necessário criar um arquivo que defina as áreas a serem ignoradas. O formato do arquivo é JSON (ou na verdade um superconjunto chamado HOCON) e tem o seguinte formato:
exclusions: [
{
page : 2
x1 : 300 // entries without a unit are in pixels. Pdfs are rendered by default at 300DPI
y1 : 1000
x2 : 550
y2 : 1300
} ,
{
// page is optional. When not given, the exclusion applies to all pages.
x1 : 130.5 mm // entries can also be given in units of cm, mm or pt (DTP-Point defined as 1/72 Inches)
y1 : 3.3 cm
x2 : 190 mm
y2 : 3.7 cm
} ,
{
page : 7
// coordinates are optional. When not given, the whole page is excluded.
}
]
Quando o arquivo de exclusão fornecido não for encontrado, ele será ignorado e a comparação será feita sem as exclusões.
As exclusões são fornecidas no código da seguinte forma:
new PdfComparator ( "expected.pdf" , "actual.pdf" ). withIgnore ( "ignore.conf" ). compare ();
Alternativamente, uma exclusão pode ser adicionada por meio da API da seguinte maneira:
new PdfComparator ( "expected.pdf" , "actual.pdf" )
. withIgnore ( new PageArea ( 1 , 230 , 350 , 450 , 420 ))
. withIgnore ( new PageArea ( 2 ))
. compare ();
Quando quiser comparar arquivos PDF protegidos por senha, você pode fornecer a senha ao Comparador por meio dos métodos withExpectedPassword(String password) ou withActualPassword(String password) respectivamente.
new PdfComparator ( "expected.pdf" , "actual.pdf" )
. withExpectedPassword ( "somePwd" )
. withActualPassword ( "anotherPwd" )
. compare ();
PdfCompare pode ser configurado com um arquivo de configuração. O arquivo de configuração padrão é chamado "application.conf" e deve estar localizado na raiz do classpath.
PdfCompare usa Lightbend Config (anteriormente chamado TypeSafe Config) para ler seus arquivos de configuração. Se quiser especificar outro arquivo de configuração, você pode descobrir mais sobre isso aqui: https://github.com/lightbend/config#standard-behavior. Em particular, você pode especificar um arquivo de configuração de substituição com o argumento de linha de comando -Dconfig.file=path/to/file.
Como alternativa, você pode especificar parâmetros por meio de variáveis de ambiente do sistema ou como um parâmetro Jvm com -DvariableName=
Outra maneira de especificar um local de configuração diferente programaticamente é criar um novo ConfigFileEnvironment(...) e passá-lo para PdfCompare.withEnvironment(...).
Todas as configurações que podem ser alteradas através do arquivo application.conf também podem ser alteradas programaticamente através da API. Para fazer isso você pode usar o seguinte código:
new PdfComparator ( "expected.pdf" , "actual.pdf" )
. withEnvironment ( new SimpleEnvironment ()
. setActualColor ( Color . green )
. setExpectedColor ( Color . blue ))
. compare ();
O SimpleEnvironment delega todas as configurações que não foram atribuídas ao ambiente padrão.
Através do ambiente você pode definir as configurações de memória (veja acima) e as seguintes configurações:
DPI=300
Define o DPI com o qual as páginas PDF são renderizadas. O padrão é 300.
cor esperada = 00B400 (VERDE)
A cor esperada é a cor usada para pixels que eram esperados, mas não existem. As cores são especificadas no formato HTML-Stlye (sem '#' inicial): Os dois primeiros caracteres definem a parte vermelha da cor em hexadecimal. Os próximos dois caracteres definem a parte verde da cor. Os dois últimos caracteres definem a parte azul da cor a ser usada.
realColor=D20000 (VERMELHO)
A cor real é a cor usada para pixels que existem, mas não eram esperados. As cores são especificadas no formato HTML-Stlye (sem '#' inicial): Os dois primeiros caracteres definem a parte vermelha da cor em hexadecimal. Os próximos dois caracteres definem a parte verde da cor. Os dois últimos caracteres definem a parte azul da cor a ser usada.
tempDir=System.property("java.io.tmpdir")
Define o diretório onde gravar arquivos temporários. O padrão é o padrão java para java.io.tmpdir, que geralmente determina um padrão específico do sistema, como /tmp na maioria dos sistemas unix.
permitidoDifferenceInPercentPerPage=0,2
Porcentagem de pixels que podem diferir por página. O padrão é 0. Se por algum motivo sua renderização estiver um pouco errada ou você permitir alguma margem de erro, você pode configurar uma porcentagem de pixels que serão ignorados durante a comparação. Dessa forma, uma diferença só é relatada quando mais do que a porcentagem determinada de pixels diferem. A porcentagem é calculada por página. Não que as diferenças ainda estejam marcadas no arquivo de saída, quando você adicionaEqualPagesToResult.
paraleloProcessing=true
Quando definido como falso, desativa todo o processamento paralelo e processa tudo em um único thread.
addEqualPagesToResult=true
Quando definido como falso, apenas as páginas com diferenças são adicionadas ao resultado e este é o documento PDF de diferença resultante.
failOnMissingIgnoreFile=falso
Quando definido como verdadeiro, um arquivo ignorado ausente leva a uma exceção. Caso contrário, ele será ignorado e apenas mensagens de log de nível informativo serão gravadas.
Existem algumas implementações diferentes de CompareResults com características diferentes. O pode ser usado para controlar certos aspectos do comportamento do sistema, em particular o consumo de memória.
É bom conhecer alguns detalhes internos ao usar o PdfCompare. Aqui está, em poucas palavras, o que o PdfCompare faz quando compara dois PDFs.
PdfCompare usa a biblioteca Apache PdfBox para ler e escrever PDFs.
Portanto, comparar PDFs grandes pode consumir muita memória. Ainda não encontrei uma maneira de escrever a diferença em PDF página por página de forma incremental com o PdfBox, mas existem algumas soluções alternativas.
Atualmente existem dois CompareResults diferentes, que possuem estratégias diferentes para trocar páginas em disco e, assim, limitar o consumo de memória.
Uma implementação CompareResult diferente pode ser usada da seguinte forma:
new PdfComparator ( "expected.pdf" , "actual.pdf" , new CompareResultWithPageOverflow ()). compare ();
Existem também algumas configurações internas para limites de memória, que podem ser alteradas. Basta adicionar um arquivo chamado “application.conf” à raiz do classpath. Este arquivo pode ter algumas ou todas as configurações a seguir para substituir os padrões fornecidos aqui:
imageCacheSizeCount=30
Quantas imagens são armazenadas em cache pelo PdfBox
maxImageSizeInCache=100000
Um tamanho máximo aproximado de imagens armazenadas em cache, para evitar que imagens muito grandes sejam armazenadas em cache
mergeCacheSizeMB = 100
Quando os PDFs são parcialmente gravados e posteriormente mesclados, esse é o cache de memória configurado para a instância do PdfBox que faz a mesclagem.
swapCacheSizeMB=100
Quando os PDFs são gravados parcialmente, esse é o cache de memória configurado para a instância do PdfBox que faz as gravações parciais.
documentCacheSizeMB=200
Este é o tamanho do cache configurado para a instância PdfBox, que carrega os documentos que são comparados.
paraleloProcessing=true
Quando definido como falso, desativa todo o processamento paralelo e processa tudo em um único thread.
globalTimeoutInMinutes=15
Defina o tempo limite geral. Esta é uma medida de segurança para detectar possíveis impasses. Comparações complexas podem demorar mais, portanto esse valor pode precisar ser aumentado.
executorTimeoutInSeconds=60
Define o tempo limite para aguardar a conclusão dos executores após o globalTimeout ser atingido. É improvável que você precise mudar isso.
Portanto, nesta configuração padrão, o PdfBox deve usar até 400 MB de Ram para seus caches, antes de trocar para o disco. Tenho uma boa experiência em conceder um espaço de heap de 2 GB à JVM.
Muito obrigado a Chethan Rao [email protected] por me ajudar a diagnosticar problemas de falta de memória e fornecer a ideia de gravações parciais e mesclagem dos PDFs gerados.