O HotPDF compila e corre sob Free Pascal 3.2.2 com Lazarus, e o resumo honesto desse port são duas frases. A criação de documentos, o carregamento, a gravação, a compressão, a descompressão, a encriptação e a desencriptação funcionam todas em backends só com Pascal, pelo que uma aplicação Lazarus pode produzir e consumir PDF real sem qualquer dependência C. Os codecs de imagem nativos opcionais não, porque os objetos Win64 pré-compilados usam um sabor de COFF que nenhum linker do Free Pascal consegue consumir, pelo que nessa toolchain os pontos de entrada resolvem para stubs que falham em modo fechado
Ir de "compila" para "funciona" levou um conjunto específico de correções, e cada uma delas é uma armadilha que vai apanhar qualquer outra base de código Delphi a mudar para Free Pascal. Vale a pena escrevê-las pela ordem em que doeram
Porque é que uma unidade compilar não prova nada?
Porque uma unidade Pascal pode referenciar um símbolo que nunca fará nada de útil e ainda assim satisfazer o compilador. No ponto em que todas as 113 unidades da biblioteca compilaram limpas sob Free Pascal, os tratadores de contentores de arquivo funcionavam genuinamente, verificados por um teste de fumo que abria um CBZ e o convertia para PDF. O achatamento de formulários XFA não funcionava de todo, porque achatamento tem de descomprimir o stream de pacotes /XFA comprimido e o ponto de entrada deflate ainda era um stub. Nada na saída da compilação distinguia esses dois casos
A regra que daí saiu é curta. Antes de escrever numa nota de lançamento que uma funcionalidade funciona numa nova toolchain, escreva uma sonda de runtime que exercite a funcionalidade de ponta a ponta nessa toolchain. Cobertura de compilação é um pré-requisito, nunca evidência. O quadro mais amplo do que o port cobre está nas notas de suporte a Free Pascal e Lazarus Win64
Uma exceção dentro de um stub cdecl não chega ao chamador
Esta merece a sua própria secção porque o sintoma é tão enganador. As unidades de stub expõem pontos de entrada C da forma como uma biblioteca estática o faria, pelo que um stub se parece com isto
// Parece razoável. Não é.
function inflate(Strm: Pointer; Flush: Integer): PtrUInt; cdecl;
public name 'inflate';
begin
raise ENotSupportedException.Create('codec unavailable');
end;
No Free Pascal para Win64 essa exceção não se propaga ao chamador. Não há nenhum tratador try..except que a veja, porque desenrolar através de uma fronteira cdecl declarada desta forma não transporta o quadro de exceção Pascal; o processo termina com código de saída 217. Do lado da aplicação não há erro, nem mensagem, nem linha de log, apenas um programa que desaparece. Isso é estritamente pior do que uma resposta errada, porque uma resposta errada pode ser tratada
A correção tentadora é fazer o stub devolver um código de falha em vez disso, e para inflate isso está certo porque o zlib tem um retorno de erro bem definido. Está errado em geral: um stub para jpeg_read_header que devolve zero diz ao chamador para continuar com uma estrutura que ninguém inicializou. A correção duradoura é bloquear no ponto de entrada Pascal em vez de dentro do stub com forma C, usando a convenção de falha que essa API já tem
function TryDecodeJPEG(const Data: TBytes; out Bitmap: TBitmap): Boolean;
begin
{$IFDEF FPC}
// Recusar antes de o stub ser sequer alcançado, com a convenção de
// falha desta própria API em vez de uma exceção através de cdecl
Bitmap := nil;
Result := False;
Exit;
{$ENDIF}
Result := DecodeJPEGNative(Data, Bitmap);
end;
O paszlib não é o zlib, e a diferença são duas classes de documento
A implementação Pascal de deflate disponível no Free Pascal trata dois enquadramentos: o wrapper zlib e o deflate cru. Não trata o enquadramento gzip, que o zlib seleciona através de valores de windowBits de 16 a 31, e não trata o modo de deteção automática que os valores de 32 a 47 selecionam. O HotPDF precisa de ambos. O caminho seguro de importação SVG pede 31, e o carregador tem uma escada de fallback que pede 47 quando o enquadramento de um stream é ambíguo. Saltar qualquer um deles e uma família inteira de documentos deixa de abrir, com um erro de descodificação que aponta para o stream em vez de para o enquadramento em falta
Há uma segunda incompatibilidade, mais aguda. O registo z_stream que o paszlib declara não tem o mesmo layout de memória que o de C: o seu campo msg é uma short string em vez de um ponteiro, e total_in e total_out são de 64 bits onde o ABI de C tem palavras de máquina. Um registo de chamador portanto não pode ser passado diretamente. O arranjo funcional é manter o estado do paszlib por trás do ponteiro state que o registo público já reserva, e copiar os campos públicos para dentro e para fora à volta de cada chamada. O CRC do gzip e o trailer de comprimento de oito bytes são contabilizados nessa mesma camada de shim, que é o lugar natural para eles, já que é ela que já detém a decisão de enquadramento
Passar um array dinâmico a um parâmetro var sem tipo
Este é o bug com maior probabilidade de estar neste momento no seu código. Quando passa um array dinâmico a um parâmetro var sem tipo, o que o chamado recebe é o endereço da variável do array, que é o endereço de um ponteiro, não o endereço do conteúdo. Por isso uma leitura para ele sobrescreve a própria variável e o que estiver ao lado
var
FBuffer: TBytes;
begin
SetLength(FBuffer, 65536);
// Errado: entrega o endereço da variável FBuffer
FStream.Read(FBuffer, Length(FBuffer));
// Certo: entrega o endereço do primeiro byte do conteúdo
FStream.Read(FBuffer[0], Length(FBuffer));
end;
No Delphi a forma errada frequentemente parece funcionar, porque o que corrompe é uma posição adjacente da pilha que nada lê depois. No Free Pascal a mesma linha dá uma falha de segmentação à primeira utilização. O que a torna tão difícil de detetar a olho é que os arrays estáticos não têm esse problema, já que uma variável de array estático é o seu próprio conteúdo, pelo que ambas as escritas estão corretas no mesmo ficheiro dependendo da declaração algumas centenas de linhas acima
Contentores ZIP sem System.Zip
O Free Pascal não tem equivalente da unidade zip da RTL, e a alternativa disponível tem tanto uma superfície de API diferente como falta de suporte para a encriptação antiga que formatos de contentores mais velhos ainda usam, pelo que um pequeno leitor dentro da biblioteca se revelou mais curto do que adaptar-se a ela. Dois detalhes de formato custaram tempo e são fáceis de errar
O primeiro é o byte de verificação do cabeçalho de encriptação. O seu décimo segundo byte é normalmente o byte alto do CRC, mas quando o bit 3 da flag de uso geral está ativo, o que significa que os tamanhos vivem num descritor de dados final e o CRC ainda não é conhecido, o byte de verificação vem do byte alto do tempo de modificação em vez disso. Implemente só a forma com CRC e todos os arquivos escritos em modo streaming rejeitam uma palavra-passe correta. O segundo é o campo extra ZIP64: os seus três campos de 64 bits aparecem numa ordem fixa mas só são escritos quando o campo de 32 bits correspondente está saturado, pelo que lê-los em desvios fixos funciona nos arquivos que testou e falha no seguinte. Analise-os por posição contra que campos de 32 bits estão saturados
Uma conveniência que vale a pena conhecer: o stream de descompressão do Free Pascal recebe um segundo argumento de construtor que salta o cabeçalho zlib, que é exatamente o que as entradas ZIP precisam, já que guardam deflate cru. Esse caminho não toca de todo no shim zlib da biblioteca, pelo que não é afetado pelo backend C em falta
Transparência de glifos coloridos sob a LCL
Ler o canal alfa de um glifo colorido rasterizado é o único detalhe de gráficos sem tradução direta. A classe PNG da LCL não tem nenhum acessor de scanline que exponha o alfa, e atribuir um PNG a um bitmap descarta-o, pelo que um emoji colorido chega totalmente opaco e compõe com uma caixa preta atrás. O caminho funcional é a imagem de interface: criá-la a partir do PNG, e depois ler píxeis através do acessor de cor, lembrando que as suas componentes são de 16 bits e precisam de ser deslocadas oito para baixo para se tornarem bytes. Essa superfície também usa a ordem natural de linhas de cima para baixo, pelo que a inversão Height - 1 - Y de que o código de scanline da VCL precisa tem de ser removida em vez de portada
Duas notas de sistema de compilação antes de reportar um bug
Uma reconstrução completa ocasionalmente falha com um símbolo indefinido cujo nome termina num sufixo $crc e um valor hexadecimal. Esse sufixo é calculado a partir dos tipos de parâmetros, e deixa de corresponder quando uma compilação constrói uma unidade contra duas versões de interface diferentes na mesma passagem. Voltar a correr a compilação resolve-o; a assinatura não está errada
Segundo, o Free Pascal 3.2.2 não tem métodos anónimos, por isso onde quer que a biblioteca usasse closures para ligar uma pipeline paralela a compilação Free Pascal toma em alternativa um fallback série determinístico. A saída é idêntica, o débito não; se depender de renderização paralela de páginas, essa é uma razão para ficar no Delphi por agora, e o design da pipeline está descrito no artigo sobre a pipeline de renderização paralela. A situação dos codecs de imagem é o outro sítio onde a escolha de toolchain muda capacidades em vez de apenas velocidade, pelo que uma implantação Lazarus deve planear os seus formatos de imagem em consequência; a matriz atual por toolchain está na página de produto do HotPDF Delphi PDF component