O PDFlibPas pode codificar imagens bilevel como JBIG2 por meio de dois backends diferentes. Um é um encoder MMR em Object Pascal nativo que está sempre presente. O outro é um encoder externo de symbol dictionary que produz saída substancialmente menor em texto digitalizado, e é opcional: um projeto precisa linkar a unidade de backend para que ele exista. Essa distinção é a origem da maior surpresa comum com este recurso, então vale enunciar primeiro: o DefaultJBIG2EncodeOptions solicita o encoder externo por padrão, e quando a unidade de backend não está linkada a solicitação silenciosamente recua para o caminho MMR em Pascal
No Delphi e no C++Builder o backend externo é um conjunto de objetos estáticos pré-construídos. No Free Pascal ele precisou se tornar uma DLL, e o caminho até essa conclusão é uma história de linker útil para qualquer um que já tentou linkar objetos C++ em um programa Free Pascal
O registro é o contrato
A unidade de backend se registra a partir de sua seção de inicialização chamando RegisterJBIG2EncoderBackend. Os chamadores a solicitam pelo bit de opções, PDF_JBIG2_OPTION_EXTERNAL_ENCODER, que tem o valor 4, ou pelo parâmetro UseExternalEncoder dos pontos de entrada estendidos de imagem. O guarda-chuva da biblioteca deliberadamente não puxa a unidade de backend, porque carregar um grande conjunto de objetos deve ser decisão de cada projeto; na árvore do C++Builder, por exemplo, ele é incluído explicitamente pelos projetos que o querem
A consequência para os chamadores é que solicitar o encoder externo é uma preferência, não uma garantia, e um build que esquece a unidade produz arquivos maiores em vez de um erro. Se o tamanho da saída importa o suficiente para pedir o encoder melhor, importa o suficiente para verificar que você o obteve
uses
PDFlibrary,
{$IFDEF FPC}
PDFlibJBIG2EncDLL; // backend dinâmico para Free Pascal
{$ELSE}
PDFlibJBIG2EncC; // conjunto de objetos estáticos para Delphi / C++Builder
{$ENDIF}
var
Pdf: TPDFlib;
ImageId: Integer;
begin
Pdf := TPDFlib.Create(nil);
try
Pdf.NewDocument;
Pdf.NewPage;
// Interpolate, SymbolExtract, UseExternalEncoder, SkipBlackDots,
// BlackDotSize, LossyLevel
ImageId := Pdf.AddImageJBIG2FromFileEx('scan-page-1.tif',
0, 1, 1, 0, 0, 0);
if ImageId = 0 then
raise Exception.Create('JBIG2 encoding failed');
Pdf.SaveToFile('archive.pdf');
finally
Pdf.Free;
end;
end;
Compilar a unidade foram duas linhas. Símbolos era o trabalho
Fazer a própria unidade de backend compilar sob Free Pascal levou exatamente duas mudanças: definir o dialeto do assembler e substituir um construtor de configurações de formato baseado em record pela variável global padrão. Isso reflete bem o quanto Pascal direto é portável entre os dois compiladores
O lado dos símbolos era o trabalho de verdade. O conjunto de objetos referencia 176 símbolos C. Desses, 128 já tinham implementações Pascal dentro da unidade e só precisavam de nomes de exportação anexados, porque o Delphi usa o nome da função como nome de símbolo enquanto o Free Pascal exige uma declaração de nome público explícita. Vinte e sete eram compartilhados com o codec JPEG 2000 e precisavam ser exportados de exatamente um lugar, já que defini-los duas vezes quebra qualquer programa que linka ambos. Os 21 restantes eram entradas de plataforma e runtime C, dezesseis funções de arquivo Win32 mais algumas chamadas de biblioteca padrão, e foram para uma nova unidade de compatibilidade
Nada disso é conceitualmente difícil, e tudo é necessário antes de o linker sequer tentar. O linker foi onde parou
Três rotas de link, três becos sem saída
O linker interno do Free Pascal não consegue ler os arquivos objeto, porque foram produzidos por um compilador que emite seções COMDAT associativas e o linker interno informa que não as suporta. Isso é uma recusa seca, não um aviso
Trocar por um linker externo parecia a resposta. O linker binutils empacotado com o Free Pascal crasha de imediato ao aplicar section garbage collection a este archive, e esse flag faz parte do conjunto fixo de parâmetros que o Free Pascal passa para o alvo Windows de 64 bits, então não pode ser removido da linha de comando; as chaves documentadas para suprimi-lo são ignoradas nesse caminho. Fornecer um binutils muito mais novo falha de outro jeito: ele não consegue processar de forma alguma o link script do Free Pascal, produzindo uma saída vazia sem o script e uma parede de erros de relocação com ele
Um limite descoberto no caminho vale a pena conhecer mesmo que você nunca encontre o problema do linker. O linker externo resolve os caminhos de arquivos objeto relativos ao diretório de saída do executável em vez da árvore de código-fonte, então uma diretiva relativa de include de objeto só funciona quando o diretório de saída por acaso coincide com o diretório de trabalho em tempo de compilação. Uma biblioteca não pode presumir isso sobre o projeto de um consumidor, o que por si só é uma razão para preferir uma biblioteca linkada a objetos soltos
Por que um compilador C++ diferente não ajuda
A próxima ideia óbvia é reconstruir o lado C++ com um compilador cujos objetos o Free Pascal consiga ler. Também não funciona, e a razão é fundamental em vez de uma questão de chaves. Uma unidade de tradução C++ mínima contendo um template, compilada com todo recurso de geração de código desativado, ainda emite weak external symbols, porque a instanciação de template e inline os produz por construção. O Free Pascal rejeita essa classe de símbolo de forma categórica. O sentido inverso também falha: um linker C++ mainstream não consegue consumir objetos do outro compilador por causa do mesmo tratamento de seções COMDAT
Então o código C++ não pode ser entregue como objetos ao Free Pascal por nenhuma rota disponível. Pode ser entregue como uma DLL, que foi o que aconteceu: o encoder e sua dependência de processamento de imagem são construídos em uma biblioteca expondo dois pontos de entrada C flat, e a unidade de backend do Free Pascal os vincula dinamicamente e se registra exatamente como o backend estático faz. O caminho Delphi e C++Builder não foi tocado de forma alguma, que é o desfecho certo; um problema de portabilidade em uma toolchain não deve perturbar a toolchain que já funcionava
A polaridade é a única coisa que vai te morder
Entre um bitmap bilevel do Windows e um encoder JBIG2 há uma incompatibilidade de convenção que nenhum sistema de tipos vai capturar. Uma scanline de device-independent bitmap de um bit por pixel trata um bit definido como branco. O encoder trata um bit definido como preto. Passe as scanlines sem alterar e você obtém um stream JBIG2 perfeitamente válido do negativo fotográfico da sua página
// DIB de um bit: bit definido significa branco. Encoder JBIG2: bit
// definido significa preto. Inverta cada byte na entrada
for I := 0 to RowBytes - 1 do
Row[I] := Row[I] xor $FF;
O método de verificação importa tanto quanto a correção. Comparar comprimentos de stream comprimido não diz nada, porque uma imagem negativa comprime para um tamanho parecido. Olhar a página prova apenas que não está visivelmente invertida. A verificação confiável é renderizar a saída dos dois caminhos de codificação, Pascal nativo e externo, para PNG e compará-los byte a byte: ambos os encoders são lossless na mesma imagem de origem, então qualquer coisa que não seja uma correspondência exata é um bug em um deles. Essa comparação agora é um teste de regressão permanente, e é o tipo de asserção que vale construir sempre que duas implementações devem concordar exatamente
Qual backend usar
Para conteúdo bilevel geral, meios-tons com dithering, line art, gráficos mistos, o encoder MMR Pascal nativo é adequado e não tem custo de implantação. Para texto digitalizado, que é o caso para o qual o JBIG2 foi projetado, o encoder externo de symbol dictionary é onde mora a redução de tamanho, porque ele fatora formas de glifos repetidas em um dicionário em vez de recodificar cada ocorrência. Se você produz arquivos de documentos digitalizados, essa diferença é grande o suficiente para mudar o planejamento de armazenamento
A questão a montante, como a imagem bilevel é produzida em primeiro lugar, importa tanto quanto para o tamanho da saída; a renderização monocromática baseada em regiões é coberta em o artigo de renderização de regiões monocromáticas, e a estratégia de tamanho de documento inteiro em otimização de tamanho de arquivo PDF e subsetting de fontes. Para conjuntos de varreduras com páginas repetidas, a deduplicação muitas vezes supera uma compressão melhor, que é o assunto de deduplicação perceptual de imagens. A disponibilidade de toolchain e backend por plataforma está listada na página de produto da losLab PDF Developer Library