O HotPDF, o componente PDF nativo para Delphi e C++Builder, importa metarquivos EMF e WMF do Windows interpretando cada registro GDI diretamente em operadores PDF em vez de achatar o arquivo para um bitmap: preenchimentos gradientes viram padrões de shading axial do PDF, pincéis de hachura viram padrões de ladrilho (tiling) do PDF, e um controle centralizado de estado de path bloqueia registros malformados de corromper a saída. Todo gráfico que um TChart, uma superfície GDI+ ou um simples TCanvas consiga exportar como um metarquivo aprimorado (enhanced metafile) é candidato a esse caminho, e a diferença aparece no instante em que alguém dá zoom na página ou a envia para uma impressora de alta resolução
A alternativa que a maioria dos desenvolvedores Delphi adota por padrão é rasterizar o metarquivo para um bitmap antes de colocá-lo na página, e o custo só aparece depois: um gráfico de barras que estava nítido na tela fica visivelmente pixelado no momento em que o PDF é impresso a 600 DPI ou projetado em uma tela de sala de reunião, e uma região de CAD preenchida com hachura vira um único retângulo cinza chapado se o estilo de preenchimento não for preservado. Ler o metarquivo como um programa em vez de uma imagem é o que evita os dois problemas, e é o caminho mais difícil de implementar corretamente, motivo pelo qual vale conhecer as armadilhas abaixo antes de um relatório ir para produção
Por que interpretar um metarquivo em vez de achatá-lo para bitmap?
O HotPDF mantém a importação de EMF e WMF no caminho vetorial porque um metarquivo do Windows é uma sequência gravada de chamadas de desenho GDI, não uma imagem, e reproduzir essas chamadas como operadores PDF de path, texto e shading é o que permite que o resultado dê escala junto com o resto da página. THPDFPage.ShowMetafile e sua contraparte ShowMetafileEx são os pontos de entrada que a aplicação chama, e ambos entregam o metarquivo para THPDFWmf, a classe que percorre cada registro GDI e o traduz. A distinção não é absoluta, e o HotPDF não finge que seja: um registro de metarquivo que é genuinamente dado raster, um blit de bitmap StretchDIBits, por exemplo, é embutido como um Image XObject real do PDF por meio de AddImage e ShowImage, o mesmo par de chamadas por que passa qualquer outra imagem da página, em vez de ser forçado a operadores de path que não conseguem expressar uma fotografia. Linhas, preenchimentos e texto permanecem vetoriais; pixels que já eram pixels na origem permanecem pixels na saída. A chamada mais simples não precisa de nada além do metarquivo carregado:
var
Pdf: THotPDF;
Chart: TMetafile;
begin
Pdf := THotPDF.Create(nil);
Chart := TMetafile.Create;
try
Chart.LoadFromFile('quarterly-revenue.emf'); // exported from TChart or GDI+
Pdf.FileName := 'quarterly-report.pdf';
Pdf.BeginDoc;
Pdf.CurrentPage.ShowMetafile(Chart);
Pdf.EndDoc;
finally
Chart.Free;
Pdf.Free;
end;
end;
Como o interpretador converte coordenadas GDI para o espaço de página do PDF?
O HotPDF resolve isso com uma única passagem sobre o próprio fluxo de registros do metarquivo, em vez de uma segunda implementação do GDI. THPDFWmf.Analyse lê o cabeçalho do metarquivo por meio da chamada Win32 GetEnhMetaFileHeader, reinicia seu estado de desenho interno e chama EnumEnhMetafile, a mesma API de enumeração que um visualizador de metarquivos usaria, de modo que cada registro EMR_* chega a THPDFWmf.ExecuteRecord na ordem em que foi originalmente gravado. O GDI expressa coordenadas de cima para baixo em unidades de dispositivo ou lógicas escolhidas pelo próprio modo de mapeamento do metarquivo; uma página PDF é de baixo para cima em pontos de espaço de usuário, o sistema de coordenadas coberto em o modelo de desenho de canvas do HotPDF para paths e preenchimentos. Cada manipulador de registro resolve essa incompatibilidade por meio de ScaleX e ScaleY, que chamam ProjectX e ProjectY para reproduzir a própria fórmula de janela-para-viewport do GDI para os modos de mapeamento anisotrópico e isotrópico, de modo que uma forma gravada com cinco unidades lógicas de largura chega com a largura correta em pontos PDF, independentemente das extensões de janela e viewport que a aplicação de origem definiu
Como um preenchimento gradiente do GDI se torna um padrão de shading do PDF?
Um registro EMR_GRADIENTFILL se torna um verdadeiro padrão de shading axial Tipo 2 do PDF (ISO 32000-1 §8.7.4.5) sempre que o GDI o gravou em um dos dois modos de retângulo. THPDFWmf.VEMRGradientFill lê o próprio layout do registro diretamente do buffer de bytes bruto, seguindo a estrutura da MS-EMF §2.3.1.6: um array de vértices com cantos RGBA de 16 bits, seguido de uma lista de retângulos que cada um referencia dois desses vértices. Para GRADIENT_FILL_RECT_H, as cores variam da esquerda para a direita ao longo da linha média horizontal do retângulo; para GRADIENT_FILL_RECT_V, variam de cima para baixo ao longo da linha média vertical. De qualquer forma, as duas cores de canto e as coordenadas projetadas do retângulo vão direto para THotPDF.RegisterAxialGradient, que devolve um nome de padrão, e a página desenha o retângulo e o preenche por meio desse padrão (SetFillPattern) em vez de uma chamada plana SetRGBFillColor, de modo que um cabeçalho em faixas ao estilo planilha ou a área de plotagem gradiente de um gráfico mantém sua mesclagem em vez de colapsar para uma cor média única
O modo de triângulos Gouraud é a lacuna honesta. Quando o campo ulMode do registro informa GRADIENT_FILL_TRIANGLE, VEMRGradientFill reconhece isso, registra em log que o modo de triângulo ainda não está implementado, e pula o retângulo em vez de arriscar uma aproximação de duas cores. A interpolação por vértice e por pixel em uma malha de triângulos arbitrária não se reduz a um shading axial ou radial de dois pontos, e expressá-la corretamente significaria emitir um shading de malha PDF Tipo 4 ou Tipo 5, a mesma família de shading que o renderizador de páginas do HotPDF também deixa sem pintar ao ler um PDF de volta. Dois caminhos de código sem relação entre si chegam à mesma fronteira: shadings de malha são a lacuna tanto no lado de escrita quanto no de leitura, e um diagrama de origem que usa triângulos Gouraud para um brilho radial suave recai para o que quer que fosse o último pincel sólido, não uma aproximação renderizada
Pincéis de hachura viram padrões de ladrilho, não cinza achatado
Um pincel de hachura do GDI mantém sua textura no PDF porque THPDFWmf.SetBrushColor verifica CurrentBrush.lbStyle em busca de BS_HATCHED antes de sequer recair para um preenchimento sólido, direcionando esse caso para SetHatchBrushPattern. Esse método escreve um content stream PDF de 8 por 8 unidades composto de operadores de linha traçada, m, l e S, escolhidos de acordo com o estilo de hachura do GDI: um único traço horizontal ou vertical para HS_HORIZONTAL e HS_VERTICAL, três diagonais paralelas para HS_FDIAGONAL e HS_BDIAGONAL, e as combinações horizontal-mais-vertical ou ambas as diagonais para HS_CROSS e HS_DIAGCROSS. THotPDF.RegisterTilingPattern registra esse content stream como um padrão de ladrilho colorido (PaintType 1, ISO 32000-1 §8.7.3.1) com XStep e YStep de 8 unidades, e a página preenche por meio de SetFillPattern da mesma forma que um shading axial. Uma planta baixa de CAD ou um desenho de engenharia que se apoia em preenchimentos de hachura para distinguir materiais preserva essa linguagem visual no PDF em vez de perder todas as regiões para um cinza idêntico
Nem todo pincel recebe esse tratamento, e vale conhecer a lacuna antes de um import de CAD ir para produção. EMR_CREATEDIBPATTERNBRUSHPT, o registro para um pincel de padrão de imagem bitmap personalizado em vez de um dos seis estilos de hachura padrão do GDI, apenas registra seu handle para que os registros posteriores SELECTOBJECT e DELETEOBJECT permaneçam consistentes; o HotPDF ainda não expõe um pipeline de recurso Pattern do PDF para imagens de ladrilho arbitrárias, de modo que selecionar esse pincel recai para um fallback de cor sólida em vez da textura original. Se um preenchimento renderiza chapado onde o original claramente usava uma textura de imagem repetida, o pincel de origem quase certamente é um padrão DIB personalizado em vez de uma hachura padrão, e esse é o único caso que vale a pena verificar manualmente primeiro. Configurar uma importação para um desenho assim ainda passa pelo mesmo objeto de opções:
var
Pdf: THotPDF;
Drawing: TMetafile;
Options: THPDFEmfOptions;
begin
Pdf := THotPDF.Create(nil);
Drawing := TMetafile.Create;
Options := THPDFEmfOptions.Create;
try
Drawing.LoadFromFile('floor-plan.emf');
Options.Assign(Pdf.EmfOptions); // start from the document-wide defaults
Options.Redraw := False; // interpret the original EMF bytes, no GDI re-record pass
Options.ShowNullBrush := True; // keep explicitly unfilled CAD regions visible
Options.UseFrame := True; // clip output to the frame the EMF header declares
Pdf.FileName := 'floor-plan.pdf';
Pdf.BeginDoc;
Pdf.CurrentPage.ShowMetafileEx(Drawing, Options);
Pdf.EndDoc;
finally
Options.Free;
Drawing.Free;
Pdf.Free;
end;
end;
O que impede um metarquivo malformado de corromper a página?
A resposta do HotPDF é um único portão no topo de ExecuteRecord, em vez de uma verificação defensiva repetida em cada um dos seus aproximadamente oitenta manipuladores de registro. Um bracket de path do GDI, aberto por EMR_BEGINPATH e fechado por EMR_ENDPATH ou EMR_ABORTPATH, é rastreado por uma propriedade privada PathContinue apoiada pelo campo FPathContinue. Enquanto esse bracket estiver aberto, ExecuteRecord deixa passar apenas registros de construção de path — as variantes de move, line, polyline, polygon, polybezier e polydraw, mais CLOSEFIGURE — e um pequeno conjunto de registros de transformação e estado de DC, como SETWORLDTRANSFORM, SAVEDC e RESTOREDC. Qualquer outro tipo de registro que chegue a ExecuteRecord enquanto o bracket estiver aberto, um EXTTEXTOUT perdido ou um blit de bitmap, por exemplo, é descartado de forma centralizada com um único Exit no instante em que chega
Esse portão existe porque um bracket de path em um metarquivo montado à mão, gerado por ferramenta ou simplesmente corrompido não tem garantia de conter apenas o que um arquivo bem formado colocaria entre seus registros de abertura e fechamento. Um registro de saída de texto pousando entre EMR_BEGINPATH e EMR_ENDPATH, sem um portão, ou poluiria a geometria do path em construção, ou emitiria um operador PDF de exibição de texto no meio de uma sequência que deveria ser pura construção de path, e os dois modos de falha são do tipo que aparece em uma única entrada malformada vinda de uma ferramenta de terceiros, não em algo que uma suíte de testes normal costuma cobrir. Centralizar a verificação em ExecuteRecord significa que os manipuladores individuais VEMR* não precisam, cada um, se defender contra ser chamado no momento errado; o portão decide isso uma vez, antes do despacho, em vez de oitenta vezes depois dele
Colocando um gráfico vetorial ao lado de texto e imagens em uma página
Uma página de relatório raramente contém apenas um gráfico, e ShowMetafile se compõe com os demais operadores de página do HotPDF exatamente como qualquer outra chamada de desenho. Um título desenhado com TextOut, um gráfico de barras preenchido com hachura importado como EMF e um logotipo posicionado com ShowImage podem todos pousar na mesma página, no mesmo content stream, cada um mantendo sua fidelidade nativa, o padrão de composição coberto em o guia do HotPDF para organizar texto, fontes e imagens em um relatório:
Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Q2 Regional Sales');
Pdf.CurrentPage.ShowMetafile(RegionChart); // hatch-filled bars, still vector
Pdf.CurrentPage.ShowImage(LogoIndex, 450, 760, 90, 30, 0);
O interpretador de EMF e WMF, os padrões de shading axial que ele registra para preenchimentos gradientes e o mapeamento de padrões de ladrilho para pincéis de hachura descritos aqui vêm todos como parte do componente HotPDF padrão para Delphi e C++Builder, uma biblioteca VCL nativa sem dependência de nenhuma DLL externa para nada disso