O PDFlibPas converte metaficheiros melhorados em conteúdo real de página PDF registo a registo, em vez de os rasterizar, e é isso que mantém um gráfico importado ou um desenho CAD nítido a qualquer zoom. Esse conversor tem cerca de 6500 linhas e foi escrito contra a VCL, pelo que quando a biblioteca ganhou um alvo Free Pascal foi classificado como não portátil e substituído por um stub. Essa classificação estava errada, e a forma como estava errada é uma lição útil sobre como auditar uma dependência antes de decidir reescrever em torno dela
A superfície VCL real dessas 6500 linhas revelou-se pequena: uma classe de bitmap usada pelo seu formato de píxel, gravação em stream, handle, canvas e scanlines; uma classe de metaficheiro usada pela sua largura, altura e handle; e o tipo de cor com duas constantes. Cada uma dessas coisas já era fornecida pela unidade de gráficos da própria biblioteca, que existe precisamente para que a compilação sem VCL tenha equivalentes. O conversor não estava bloqueado pela VCL. Estava bloqueado pela unidade Windows do Free Pascal
Dividir pelo eixo de que o código realmente depende
A alteração não foi uma reimplementação. Foi uma condicional: de "compilar o stub quando construído sem a VCL" para "compilar o stub quando não se constrói para Windows". Esse é o eixo correto, e enunciar o porquê torna a diferença óbvia. Um metaficheiro melhorado é um contentor Windows. O conversor é um parser de registos GDI Windows de cima a baixo. Que a aplicação anfitriã use a VCL, outro conjunto de widgets, ou nenhum conjunto de widgets não tem nada a ver com o facto de esses registos poderem ser interpretados; que o alvo seja Windows tem tudo a ver com isso
As consequências de escolher o eixo certo saem de graça. As compilações C++Builder, que removem a definição do símbolo da plataforma Windows nesta biblioteca, mantêm o stub que lança exceção e comportam-se exatamente como antes. O macOS mantém o stub, corretamente, porque não há registos GDI para interpretar aí. As compilações Delphi VCL ficam intactas. E uma compilação Windows com um conjunto de widgets sem VCL ganha importação de EMF vetorial como efeito lateral, o que ninguém teve de implementar. Uma condicional alinhada com a dependência real transforma o trabalho de plataforma numa alteração de uma linha; uma condicional alinhada com a errada transforma-o numa reescrita que nunca entra no planeamento
A lacuna do Free Pascal eram declarações, não lógica
O que realmente faltava eram as declarações Win32 que a unidade Windows do Delphi fornece e a do Free Pascal não. Reuni-las numa única unidade de compatibilidade em vez de espalhar condicionais pelo conversor manteve o parser legível. A lista é instrutiva porque mostra quão desigual é a cobertura de headers entre as duas RTL: 113 constantes de tipos de registo de metaficheiro, duas flags de saída de texto estendida, três constantes de modo de preenchimento de gradiente, um tipo de ponteiro para tabela de handles, aliases para os registos de vértice de gradiente e primitivas, e três tipos de registo que o Free Pascal não declara de todo, cobrindo alpha blending, blitting transparente e modo de gestão de cor
Nada disso é interessante individualmente. Tudo tem de estar certo antes de o parser compilar, e uma unidade de compatibilidade é o lar natural porque pode ser comparada com a documentação dos headers como uma unidade
A que desenha silenciosamente a imagem errada
Duas dessas declarações não estão apenas em falta, estão presentes e erradas para este fim, e esta é a parte que vale a pena recordar mesmo que nunca toque num metaficheiro
O Free Pascal declara o registo de criação de brush com a estrutura de brush de execução incorporada, e o registo de caneta estendida com a estrutura de caneta de execução incorporada. Ambas essas estruturas de execução declaram o seu membro hatch como um inteiro do tamanho de um ponteiro, porque numa chamada GDI real esse membro pode transportar um handle. Um metaficheiro, no entanto, guarda sempre a forma de 32 bits, porque o layout do registo faz parte do formato de ficheiro serializado e não muda com a largura de bits do processo
Nas compilações de 32 bits as duas coincidem e nada acontece. No Win64 o membro do tamanho de um ponteiro tem oito bytes onde o ficheiro tem quatro, pelo que cada campo a seguir ao membro hatch é lido do desvio errado. Não há exceção, nem erro de análise, nem aviso. O metaficheiro simplesmente renderiza mal: cores dos bytes errados, larguras de caneta dos bytes errados, e uma imagem que parece um bug de renderização em vez de um bug de layout de estrutura. O Delphi distribui explicitamente variantes de 32 bits de ambas as estruturas por exatamente esta razão, e a unidade de compatibilidade redeclara-as da mesma forma
// Errado no Win64: Hatch tem o tamanho de um ponteiro, o ficheiro
// guarda 32 bits, e todos os campos seguintes deslocam-se em quatro
// bytes sem erro nenhum
type
TLogBrushRuntime = record
lbStyle: UINT;
lbColor: COLORREF;
lbHatch: ULONG_PTR; // 8 bytes num processo de 64 bits
end;
// Certo: o layout serializado, largura fixa independentemente da
// largura de bits
type
TLogBrush32 = record
lbStyle: UINT;
lbColor: COLORREF;
lbHatch: DWORD; // sempre 4 bytes, como guardado no metaficheiro
end;
A regra geral: qualquer estrutura que apareça tanto como argumento de API de execução como layout de campos serializado precisa de duas declarações, e a serializada deve usar tipos de largura fixa por inteiro. Membros do tamanho de um ponteiro num formato de ficheiro são sempre um bug à espera de uma compilação de 64 bits
Diferenças de assinatura pertencem a um wrapper, não a cada ponto de chamada
As diferenças restantes eram discrepâncias ordinárias de assinatura, e a forma de as absorver é um wrapper de reencaminhamento em vez de uma condicional em cada um dos pontos de chamada. A função de combinação de transformações recebe ponteiros sob Free Pascal onde o Delphi recebe parâmetros por referência, pelo que o wrapper recebe referências e passa endereços. Também copia primeiro ambos os argumentos de origem para locais, porque o conversor tem pontos de chamada em que a matriz de destino é simultaneamente uma das origens, e passar o mesmo endereço duas vezes a uma função que escreve enquanto lê produz uma transformação subtilmente errada de um modo que só aparece em conteúdo rodado
function CombineTransformCompat(var Dest: TXForm;
const A, B: TXForm): BOOL;
var
SrcA, SrcB: TXForm;
begin
// Copiar primeiro: os chamadores legitimamente passam Dest como A ou B
SrcA := A;
SrcB := B;
{$IFDEF FPC}
Result := Windows.CombineTransform(@Dest, @SrcA, @SrcB);
{$ELSE}
Result := Windows.CombineTransform(Dest, SrcA, SrcB);
{$ENDIF}
end;
Os tipos de retângulo e de ponto são o outro caso. O Free Pascal trata os registos de retângulo e de ponto do metaficheiro como tipos distintos dos gráficos gerais, pelo que oito pontos de atribuição precisaram de um cast explícito entre registos de layout idêntico. Ambos os compiladores aceitam a forma com cast, pelo que esses pontos não trazem condicional nenhuma, o que vale um pouco de feiura
O que isto muda numa implantação Free Pascal
A importação vetorial EMF funciona em Windows sob Free Pascal, produzindo o mesmo conteúdo de página que a compilação Delphi: paths como paths, gradientes como conteúdo de padrão, texto como texto. Fora de Windows, o caminho raster continua a ser a resposta, e isso é uma limitação do formato e não do port. O estado de coordenadas e clipping em que o conversor alimenta está descrito no artigo sobre o rastreador de CTM e clipping do content stream, e as primitivas vetoriais que emite estão cobertas em gráficos vetoriais, shaders e gradientes
Se estiver a auditar a sua própria base de código à procura da mesma oportunidade, o exercício útil é o que isto iniciou: listar os membros que realmente usa do framework de que pensa depender. A resposta é frequentemente muito mais curta do que a lista de imports sugere, e a restrição real está geralmente noutro sítio completamente. Os percursos de importação baseados em device context em geral estão descritos no artigo sobre pré-visualização de impressão e device context, e a cobertura de plataformas e toolchains está listada na página de produto da losLab PDF Developer Library