O HotPDF exporta uma página de qualquer documento PDF carregado para marcação SVG autónoma com uma única chamada, BuildLoadedPageSVG, que devolve o documento SVG completo como uma cadeia de caracteres. A marcação exportada transporta a geometria da página, o texto como elementos SVG text reais, imagens rasterizadas incorporadas, e o estado de traço que os operadores PDF tinham estabelecido em cada operação de desenho
É precisamente aí que a maioria dos conversores caseiros falha silenciosamente. Transformar uma página PDF em SVG parece um problema de coordenadas, mas revela-se 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 transportam cada um os seus próprios atributos de apresentação. Tudo o que o interpretador não capturar no momento em que um elemento é emitido desaparece simplesmente do resultado, e a falha é silenciosa: obtém-se um SVG válido que representa uma página subtilmente errada
Por que razão uma página PDF não se converte simplesmente em SVG?
Três desencontros tornam a conversão não trivial, e todos os três produzem um resultado que parece plausível até se comparar lado a lado com o original. O primeiro é o eixo dos y. O espaço do utilizador em 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 ao nível da página corrige as coordenadas de desenho, mas depois quebra todos os glifos, porque inverter toda a tela também espelha as formas das letras
O segundo desencontro é a herança. Em PDF, q e Q empilham e desempilham um estado gráfico que inclui a largura de linha, o remate de linha, a junção de linha, o limite de esbaimento (miter limit), a matriz de traços e a fase de traços, além do alfa. Em SVG, um elemento que não nomeia um atributo herda-o de um grupo ancestral, o que é uma regra de âmbito completamente diferente. Um exportador que só acompanha a matriz de transformação atual e esquece o estado de traço deixa que o estado restaurado após um Q se infiltre nos elementos seguintes
O terceiro é que o PDF expressa várias coisas por convenção, e não por valor. Os remates e as junções de linha são inteiros, uma 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 enrolamento em vez da cor. Cada um destes casos precisa de uma tradução, não de uma cópia
Uma chamada para o caso comum
Para a tarefa comum de exportar páginas para um visualizador web, uma ferramenta de comparação ou uma entrega de design, a superfície da API resume-se a uma função. BuildLoadedPageSVG recebe um índice de página baseado em zero, referente ao documento atualmente carregado, e devolve 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; // O LoadFromFile devolve o número 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 é disponibilizada pela ferramenta de linha de comandos do HotPDF através do comando export-svg, útil em pipelines de compilação e scripts de regressão onde se pretende uma representação de uma página comparável em texto, sem escrever uma única linha de Pascal. Por ser texto, o SVG é um complemento natural ao percurso raster descrito em renderizar uma página PDF para um bitmap: o bitmap mostra o aspeto da página, o SVG mostra do que ela é feita
Como é que o texto PDF é mapeado em elementos de texto SVG?
O HotPDF compõe a cadeia da matriz de texto como prefixo por CTM por matriz de texto por inversão de glifo, sendo a inversão de glifo uma multiplicação à direita por matrix(1,0,0,-1,0,0). Esse fator do lado direito existe apenas para anular a inversão vertical ao nível da página nas formas dos glifos, já que o texto SVG desenhado num referencial local invertido apareceria de cabeça para baixo. Colocar a correção na matriz, em vez de num código com casos especiais, faz com que o texto rodado, espelhado e inclinado saia sempre correto, sem ramificações adicionais
O posicionamento horizontal utiliza a sintaxe multivalor x do elemento SVG text, uma coordenada por carácter, acumulada a partir de cada avanço de glifo, mais o espaçamento de caracteres Tc e o espaçamento de palavras Tw em vigor nesse momento. O escalonamento horizontal Tz é incorporado nas colunas a e c da matriz de texto em vez de ser emitido em separado, pelo que um visualizador que ignore atributos de texto exóticos continua a colocar cada glifo onde o PDF o colocou. O texto produzido através de composição complexa, abordado em composição de texto de escrita complexa, segue o mesmo percurso, porque o compositor já resolveu os agrupamentos 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 rodada e uma rotação expressa no espaço de visualização com o 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 e (0,1,-1,0,H,0) para 270, sendo W e H as dimensões da página antes da rotação. Derivar estas matrizes à mão convida a erros de sinal em exatamente três pontos, pelo que o exportador as compõe através da mesma rotina de multiplicação de matrizes que trata todas as outras transformações
As imagens incorporadas precisam da sua própria inversão, porque o espaço de imagem do PDF coloca a primeira linha de amostra na aresta superior do quadrado unitário, enquanto o elemento SVG image tem um referencial local com o y para baixo. A transformação emitida é, portanto, a CTM multiplicada à direita por matrix(1,0,0,-1,0,1). Errar nesta parte produz fotografias espelhadas verticalmente numa página que, de resto, está perfeita, o tipo de defeito que um revisor humano deteta de imediato e um teste automatizado muitas vezes não deteta
O que é 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 através de uma interface de dispositivo opcional separada, pelo 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 do código compilado contra versões anteriores. Concretamente, o SVG exportado recebe palavras-chave traduzidas em vez de inteiros PDF em bruto:
// As enumerações de inteiros do PDF tornam-se atributos de palavra-chave SVG
// remate de linha 0, 1, 2 -> butt, round, square
// junção de linha 0, 1, 2 -> miter, round, bevel
//
// Uma largura de linha zero significa um traço fino (hairline) no espaço
// do dispositivo em PDF, pelo que o exportador emite
// vector-effect="non-scaling-stroke" para manter o traço visível e
// com cerca de um pixel de dispositivo de largura após a CTM
//
// f* B* b* selecionam a regra even-odd e emitem fill-rule="evenodd",
// enquanto f B b mantêm o valor predefinido do SVG, nonzero winding
A restauração de estado num Q abrange em conjunto a opacidade, a largura de linha, o remate, a junção, o limite de esbaimento, a matriz de traços e a fase de traços. Os Form XObjects aninhados capturam e restauram o mesmo conjunto completo nos seus limites, pelo que um contorno tracejado definido dentro de um carimbo não pode infiltrar o seu padrão no conteúdo de página que se segue. Para quem já acompanha o comportamento de recorte e da CTM por outros motivos, este é o mesmo modelo de estado que aparece em importação vetorial de EMF e WMF, a funcionar no sentido inverso
Limites que vale a pena conhecer antes de lançar em produção
O exportador é honesto quanto ao seu âmbito, e conhecer os limites antecipadamente sai mais barato do que descobri-los em produção. A cor chega ao dispositivo SVG através dos operadores rg, RG, g e G. Os preenchimentos estabelecidos através de um espaço de cor mais scn, forma como as cores Separation, DeviceN e ICCBased são pintadas, não chegam ao dispositivo como um trio RGB já resolvido, pelo que as páginas que usam cores diretas (spot colours) desta forma exportam a sua geometria mas não essas cores. Para origens vocacionadas para impressão, rasterize antes ou aplaine primeiro as cores diretas; o próprio modelo de pintura está descrito em renderizar cores diretas Separation e DeviceN
Duas notas mais pequenas poupam tempo de depuração. Os literais de cor hexadecimais são emitidos em maiúsculas, pelo que um teste que verifique #ff0000 falha contra um #FF0000 perfeitamente correto. E o dispositivo SVG tem contagem de referências através da sua interface, o que significa que libertá-lo é uma questão de deixar a interface sair de âmbito, e não de chamar Free sobre o objeto, uma distinção que importa se estender o dispositivo para emitir a sua própria marcação a par do conteúdo da página
A exportação SVG combina naturalmente com a comparação estrutural quando é preciso saber se um documento gerado realmente mudou entre duas compilações. O conjunto de ferramentas mais amplo em torno dos documentos carregados, desde a renderização à edição e à exportação, está documentado na página do componente PDF para Delphi HotPDF