O HotPDF exporta uma página de qualquer documento PDF carregado para marcação SVG independente com uma única chamada, BuildLoadedPageSVG, que retorna o documento SVG completo como uma string. A marcação exportada carrega a geometria da página, o texto como elementos text reais do SVG, imagens raster incorporadas e o estado de traço que os operadores do PDF haviam estabelecido em cada operação de desenho
É exatamente nesse último ponto que a maioria dos conversores caseiros falha silenciosamente. Transformar uma página PDF em SVG parece um problema de coordenadas, mas na verdade é um problema de estado. O PDF é uma máquina de pilha cujo estado gráfico muda à medida que o fluxo de conteúdo é interpretado; o SVG é uma árvore declarativa cujos elementos carregam individualmente seus próprios atributos de apresentação. Tudo o que o interpretador deixa de capturar no momento em que um elemento é emitido simplesmente desaparece da saída, e a falha é silenciosa: você obtém um SVG válido que renderiza uma página sutilmente incorreta
Por que uma página PDF não é simplesmente convertida em SVG?
Três incompatibilidades tornam essa conversão nada trivial, e todas as três produzem uma saída que parece plausível até você compará-la lado a lado com o original. A primeira é o eixo y. O espaço do usuário no PDF cresce para cima a partir do canto inferior esquerdo da página; o SVG cresce para baixo a partir do canto superior esquerdo. Uma única inversão no nível da página corrige as coordenadas de desenho, mas quebra cada glifo, porque inverter toda a tela também espelha as formas das letras
A segunda incompatibilidade é a herança. No PDF, q e Q empilham e desempilham um estado gráfico que inclui largura de linha, terminação de linha, junção de linha, limite de meia-esquadria, matriz de traço, fase do traço e alfa. No SVG, um elemento que não nomeia um atributo o herda de um grupo ancestral, o que é uma regra de escopo totalmente diferente. Um exportador que rastreia apenas a matriz de transformação atual e esquece o estado de traço permite que o estado restaurado após um Q vaze para os elementos seguintes
A terceira é que o PDF expressa várias coisas por convenção, e não por valor. Terminações e junções de linha são números inteiros, largura de linha zero significa um traço fino (hairline) no espaço do dispositivo em vez de uma linha invisível, e as variantes com asterisco dos operadores de pintura alteram a regra de preenchimento em vez da cor. Cada um desses casos exige uma tradução, não uma simples cópia
Uma única chamada para o caso comum
Para a tarefa comum de exportar páginas para um visualizador web, uma ferramenta de comparação (diff) ou uma entrega para design, a superfície da API se resume a uma única função. BuildLoadedPageSVG recebe um índice de página baseado em zero em relação ao documento atualmente carregado e retorna o documento SVG como uma AnsiString:
var
Pdf: THotPDF;
I: Integer;
Svg: AnsiString;
Output: TFileStream;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('statements.pdf', '') <= 0 then
Exit; // LoadFromFile retorna a contagem de páginas
for I := 0 to Pdf.LoadedPageCount - 1 do
begin
Svg := Pdf.BuildLoadedPageSVG(I);
if Length(Svg) = 0 then
Continue;
Output := TFileStream.Create(Format('page-%d.svg', [I + 1]), fmCreate);
try
Output.WriteBuffer(Svg[1], Length(Svg));
finally
Output.Free;
end;
end;
finally
Pdf.Free;
end;
end;
A mesma exportação é exposta pela ferramenta de linha de comando do HotPDF como o comando export-svg, útil em pipelines de build e scripts de regressão em que você deseja uma representação de página comparável por texto (diffable) sem escrever nenhum código Pascal. Como o SVG é texto, ele se torna um complemento natural do caminho raster descrito em renderização de uma página PDF para um bitmap: o bitmap mostra a aparência da página, o SVG mostra do que ela é feita
Como o texto do PDF é mapeado para elementos de texto do SVG?
O HotPDF compõe a cadeia da matriz de texto como prefixo multiplicado pela CTM, multiplicado pela matriz de texto, multiplicado pela inversão de glifo, em que a inversão de glifo é uma multiplicação à direita por matrix(1,0,0,-1,0,0). Esse fator à direita existe unicamente para cancelar a inversão vertical no nível da página aplicada às formas dos glifos, já que um texto SVG desenhado em um quadro local invertido apareceria de cabeça para baixo. Colocar a correção na matriz, em vez de em código com casos especiais, faz com que texto rotacionado, espelhado e inclinado saia correto sem ramificações extras
O posicionamento horizontal usa a sintaxe de múltiplos valores do atributo x do elemento text do SVG, uma coordenada por caractere, acumulada a partir de cada avanço de glifo somado ao espaçamento entre caracteres Tc e ao espaçamento entre palavras Tw vigentes naquele momento. O escalonamento horizontal Tz é incorporado às colunas a e c da matriz de texto em vez de ser emitido separadamente, de modo que um visualizador que ignora atributos de texto exóticos ainda posiciona cada glifo exatamente onde o PDF o colocou. O texto produzido por meio de formatação complexa, abordado em formatação de texto para scripts complexos, segue o mesmo caminho, porque o shaper já resolveu os clusters em glifos posicionados no momento em que o fluxo de conteúdo é interpretado
Rotação e imagens: duas inversões fáceis de trocar
Uma página com uma entrada /Rotate diferente de zero precisa de uma pré-transformação composta por uma inversão em relação à altura da tela rotacionada e uma rotação expressa no espaço de exibição com y para cima. As três matrizes de rotação são (0,-1,1,0,0,W) para 90 graus, (-1,0,0,-1,W,H) para 180 graus e (0,1,-1,0,H,0) para 270 graus, sendo W e H as dimensões da página antes da rotação. Derivar essas matrizes manualmente convida a erros de sinal em exatamente três pontos, portanto o exportador as compõe por meio da mesma rotina de multiplicação de matrizes que trata todas as demais transformações
Imagens incorporadas precisam de uma inversão própria, porque o espaço de imagem do PDF posiciona a primeira linha de amostra na borda superior do quadrado unitário, enquanto o elemento image do SVG usa um quadro local com y para baixo. A transformação emitida é, portanto, a CTM multiplicada à direita por matrix(1,0,0,-1,0,1). Errar esse ponto produz fotografias espelhadas verticalmente em uma página que, fora isso, está perfeita, um tipo de defeito que um revisor humano percebe instantaneamente e um teste automatizado muitas vezes não
O que o dispositivo de estado gráfico realmente preserva?
O HotPDF despacha os operadores de estado de traço w, J, j, M e d por meio de uma interface de dispositivo opcional separada, de modo que a fidelidade do traço foi adicionada sem alterar a vtable do dispositivo de conteúdo existente e sem quebrar a compatibilidade binária de código compilado contra versões anteriores. Concretamente, o SVG exportado recebe palavras-chave traduzidas em vez de inteiros brutos do PDF:
// Enumerações inteiras do PDF tornam-se atributos de palavra-chave do SVG
// terminação de linha 0, 1, 2 -> butt, round, square
// junção de linha 0, 1, 2 -> miter, round, bevel
//
// Largura de linha zero significa um traço fino (hairline) no espaço do
// dispositivo no PDF, então o exportador emite vector-effect="non-scaling-stroke"
// para manter o traço visível e com largura próxima de um pixel do dispositivo após a CTM
//
// f* B* b* selecionam a regra even-odd e emitem fill-rule="evenodd",
// enquanto f B b mantêm o padrão nonzero do SVG
A restauração de estado em um Q abrange opacidade, largura de linha, terminação, junção, limite de meia-esquadria, matriz de traço e fase do traço, tudo junto. Form XObjects aninhados capturam e restauram o mesmo conjunto completo em seus limites, de modo que uma borda tracejada definida dentro de um carimbo não consegue vazar seu padrão para o conteúdo de página que vem depois. Se você já rastreia o comportamento de recorte e da CTM por outros motivos, esse é o mesmo modelo de estado que aparece em importação de vetores EMF e WMF, funcionando na direção oposta
Limites que vale a pena conhecer antes de colocar em produção
O exportador é transparente quanto ao seu escopo, e conhecer esses limites com antecedência sai mais barato do que descobri-los em produção. A cor chega ao dispositivo SVG por meio dos operadores rg, RG, g e G. Preenchimentos estabelecidos por meio de um espaço de cor combinado com scn, que é como cores Separation, DeviceN e ICCBased são pintadas, não chegam ao dispositivo como uma tripla RGB já resolvida, de modo que páginas que usam cores especiais (spot colours) dessa forma exportam sua geometria, mas não essas cores. Para fontes voltadas à impressão, rasterize em vez disso ou nivele (flatten) as cores especiais antes; o próprio modelo de pintura é abordado em renderização de cores especiais Separation e DeviceN
Duas observações menores economizam tempo de depuração. Literais de cor hexadecimais são emitidos em maiúsculas, então um teste que verifica #ff0000 falha contra um #FF0000 perfeitamente correto. E o dispositivo SVG tem contagem de referências por meio de sua interface, o que significa que liberá-lo é uma questão de deixar a interface sair de escopo, em vez de chamar Free no objeto, uma distinção que importa caso você estenda o dispositivo para emitir sua própria marcação junto com o conteúdo da página
A exportação para SVG combina naturalmente com a comparação estrutural quando você precisa saber se um documento gerado realmente mudou entre duas builds. O conjunto mais amplo de ferramentas em torno de documentos carregados, da renderização à edição e à exportação, está documentado na página do componente PDF para Delphi HotPDF