Artigo Técnico

Importar SVG para PDF como Form XObjects Reutilizáveis

O HotPDF importa um ficheiro SVG para um PDF carregado ou criado como um Form XObject reutilizável através de ImportSVGFormXObject, que devolve o nome do recurso do formulário criado. Colocá-lo é uma chamada separada, DrawSVGFormXObject, pelo que uma única importação pode ser aplicada em muitas páginas com tamanhos diferentes sem duplicar um único byte da obra gráfica

Essa separação importa mais do que parece. Um logótipo rasterizado a 300 DPI e colocado em 400 páginas resulta em 400 cópias de um bitmap. O mesmo logótipo como Form XObject é um único objeto referenciado 400 vezes, mantém-se nítido em qualquer zoom e é impresso na resolução do dispositivo, em vez de num DPI qualquer que parecesse suficiente no momento da compilação

Diagrama de uma importação de SVG a criar um único Form XObject de PDF que o DrawSVGFormXObject coloca em muitas páginas com tamanhos diferentes, em comparação com centenas de cópias de bitmap rasterizado
Uma importação escreve a obra gráfica uma única vez como um Form XObject com os seus próprios recursos, e cada colocação é uma pequena invocação que não acrescenta bytes duplicados

Porquê um Form XObject em vez de desenhar o SVG de cada vez?

Um Form XObject é o próprio mecanismo do PDF para conteúdo reutilizável: um fluxo de conteúdo autónomo com a sua própria caixa delimitadora e dicionário de recursos, invocado pelo nome. Importar um SVG para um destes dá-lhe três propriedades que o desenho direto não oferece. A obra gráfica existe uma única vez no ficheiro. Cada colocação é uma pequena matriz e uma invocação. E as formas mantêm-se vetoriais, pelo que um visualizador a 800 por cento de zoom desenha curvas, não pixels

A subtileza está nos recursos. As fontes e os estados gráficos de um formulário têm de residir no próprio dicionário /Resources do formulário, e não nos recursos da página que por acaso estava ativa no momento da importação. O HotPDF escreve as fontes Base 14 e as entradas ExtGState de que um formulário necessita no próprio formulário, porque um formulário que pede emprestado um nome à sua primeira página anfitriã é renderizado corretamente aí, mas produz um nome de recurso não resolvido em todas as outras páginas onde for depois colocado

Duas inversões, uma para a geometria e outra para os glifos

O sistema de coordenadas do SVG cresce para baixo a partir do canto superior esquerdo; um Form XObject do PDF utiliza o espaço do utilizador do PDF, que cresce para cima a partir do canto inferior esquerdo. A transformação viewBox raiz executa uma inversão no eixo Y para que a geometria fique corretamente posicionada. O texto precisa então de uma segunda inversão, local, porque um glifo desenhado num referencial já invertido ficaria de cabeça para baixo

Diagrama das duas inversões do eixo Y entre as coordenadas SVG e PDF: a transformação do viewBox raiz corrige a geometria e os glifos de texto precisam de uma segunda inversão local para se manterem direitos
A transformação do viewBox raiz inverte o eixo Y uma única vez para que a geometria fique corretamente posicionada no espaço PDF, e o texto recebe uma segunda inversão, local, ou cada glifo chega de cabeça para baixo

Trata-se da mesma correção que surge invertida quando uma página PDF é exportada como SVG, descrita em exportar páginas PDF para SVG. Fazê-lo incorretamente é fácil de detetar em obras gráficas com texto e quase impercetível em obras sem texto, razão pela qual um logótipo com um slogan é um teste de fumo melhor do que um logótipo isolado

uses
  HPDFDoc;

var
  Pdf: THotPDF;
  SvgBytes: TBytes;
  FormName: AnsiString;
  Stats: THPDFSVGImportStatistics;
  I: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'catalogue.pdf';
    Pdf.BeginDoc;

    SvgBytes := LoadFileBytes('brand-logo.svg');
    FormName := Pdf.ImportSVGFormXObject(SvgBytes, Stats);
    if FormName = '' then
      raise Exception.Create('SVG rejected - see the import statistics');

    // Uma importação, várias colocações: os bytes da obra gráfica existem uma única vez
    for I := 1 to 40 do
    begin
      if I > 1 then
        Pdf.AddPage;
      Pdf.CurrentPage.DrawSVGFormXObject(FormName, 40, 780, 120, 40);
    end;

    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Como sobrevivem as hiperligações à viagem?

Não podem sobreviver dentro do formulário, porque um Form XObject é conteúdo e os destinos de ligação do PDF são anotações, que pertencem a uma página. Um elemento <a> no SVG de origem não pode, por isso, ser simplesmente copiado para o fluxo de conteúdo do formulário

O HotPDF resolve isto mantendo os retângulos de ligação como metadados no espaço do formulário durante a análise, e materializando-os como anotações de ligação ao nível da página em cada colocação. O DrawSVGFormXObject mapeia esses retângulos através do retângulo de destino que passar, pelo que um formulário colocado duas vezes com tamanhos diferentes obtém dois conjuntos de áreas de clique com o tamanho correto. Os retângulos de ligação são mapeados apenas através do tamanho raiz e nunca entram no fluxo de conteúdo, o que mantém a obra gráfica desenhada idêntica independentemente do número de ligações que contém. Passe AddLinks = False quando quiser a obra gráfica sem a interatividade, por exemplo numa página apenas para impressão

O que o importador recusa, e porque é essa precisamente a questão

O SVG é um dialeto XML com scripting, referências externas e expansão de entidades, o que torna um ficheiro SVG arbitrário uma entrada hostil da mesma forma que um ficheiro HTML arbitrário o é. O perfil de importação é fail-closed para tudo o que aceda para fora do documento: referências a recursos externos, scripts, atributos de manipuladores de eventos, DTDs e qualquer url() que não seja uma referência de fragmento local

Fail-closed significa que a importação falha em vez de descartar silenciosamente a construção problemática. Essa distinção é importante para um pipeline em lote, porque um descarte silencioso produz um documento ao qual falta algo que ninguém sabe que falta, enquanto uma recusa produz um ficheiro que pode ser colocado em quarentena e inspecionado. Se aceitar carregamentos de SVG de utilizadores, este é o comportamento que quer, e articula-se com o isolamento ao nível de processo descrito em isolamento de processo de codecs para imagens não confiáveis

Diagrama de um perfil de importação de SVG fail-closed que aceita funcionalidades vetoriais locais e recusa scripts, referências externas e DTDs, fazendo falhar a importação em vez de os descartar silenciosamente
O perfil de importação recusa scripts, referências externas e DTDs falhando de imediato, o que lhe dá um ficheiro para colocar em quarentena, em vez de um documento silenciosamente incompleto

Vale a pena conhecer uma fronteira interna se também renderizar fontes a cores. O subconjunto estrito de SVG utilizado para glifos a cores OpenType SVG passa por um ponto de entrada separado e mantém o seu comportamento de falha original, mais restrito. A importação de obras gráficas gerais é um percurso distinto, pelo que adicionar suporte para arcos, transparência e CSS às obras importadas não alargou aquilo que um glifo de fonte pode conter

Gradientes, máscaras e a regra da não rasterização

Os gradientes são importados como bandas de cor vetoriais delimitadas, restringidas pelo caminho de recorte da geometria original, e não como uma imagem rasterizada. As máscaras seguem a mesma abordagem: o contorno da máscara torna-se um caminho de recorte vetorial. O resultado é que uma ilustração rica em gradientes permanece independente da resolução e mantém-se pequena, enquanto um importador que rasterizasse produziria um objeto de imagem grande cuja qualidade fica fixada no momento da importação

O compromisso está na fidelidade nos casos extremos. Uma aproximação em bandas de um gradiente suave não é matematicamente idêntica ao gradiente SVG, e uma obra gráfica com dezenas de máscaras suaves sobrepostas ficará mais próxima do original se, em vez disso, a renderizar como imagem de alta resolução. Para conhecer os modelos de sombreamento que o PDF suporta nativamente, e quando recorrer diretamente a eles, veja renderizar sombreamentos axiais e radiais

// Ler as estatísticas de importação diz-lhe o que realmente foi importado
Writeln(Format('nodes=%d css rules=%d gradients=%d masks=%d',
  [Stats.NodeCount, Stats.CSSRuleCount, Stats.GradientCount,
   Stats.MaskCount]));
Writeln(Format('text runs=%d links=%d form bytes=%d',
  [Stats.TextRunCount, Stats.LinkCount, Stats.OutputBytes]));

// Colocar o mesmo formulário em dois tamanhos produz dois conjuntos de ligações corretos
Pdf.CurrentPage.DrawSVGFormXObject(FormName, 40, 700, 240, 80);
Pdf.CurrentPage.DrawSVGFormXObject(FormName, 320, 700, 120, 40);

// Colocação apenas para impressão: obra gráfica sem as áreas de clique
Pdf.CurrentPage.DrawSVGFormXObject(FormName, 40, 60, 120, 40, False);

Onde a importação de SVG se encaixa junto das outras vias vetoriais

Existem três vias de importação vetorial e não são intercambiáveis. A importação de SVG é adequada para obras gráficas produzidas por ferramentas de design e para tudo o que chega de um pipeline web. A importação de metaficheiros, abordada em importação vetorial de EMF e WMF, é adequada para conteúdo gerado por aplicações Windows e por componentes de gráficos que desenham para um contexto de dispositivo. O desenho direto através da API de página é adequado para obras gráficas calculadas pelo seu próprio código, onde não existe sequer um formato intermédio

O argumento da reutilização favorece a importação de SVG especificamente para elementos repetidos. Papéis timbrados, logótipos, marcas de água, marcas de certificação e regras decorativas são todos desenhados de forma idêntica em muitas páginas, e cada um deles corresponde a um único objeto de formulário mais uma invocação por página. Meça a diferença no tamanho do ficheiro num documento real antes de decidir que isso não importa: num catálogo de 500 páginas com um logótipo vetorial por página, a diferença entre um formulário e 500 cópias inline costuma ser de vários megabytes

A importação de SVG, a importação de metaficheiros e o desenho vetorial direto partilham o mesmo escritor de fluxo de conteúdo para Delphi e C++Builder; a lista completa de funcionalidades está disponível na página do componente PDF HotPDF para Delphi