Artículo técnico

Importar SVG a PDF como Form XObjects reutilizables

HotPDF importa un archivo SVG a un PDF cargado o creado como un Form XObject reutilizable mediante ImportSVGFormXObject, que devuelve el nombre de recurso del formulario creado. Colocarlo es una llamada aparte, DrawSVGFormXObject, de modo que una sola importación puede estamparse en muchas páginas con distintos tamaños sin duplicar un solo byte del arte

Esa separación importa más de lo que parece. Un logotipo rasterizado a 300 DPI y colocado en 400 páginas son 400 copias de un mapa de bits. El mismo logotipo como Form XObject es un solo objeto referenciado 400 veces, se mantiene nítido a cualquier nivel de zoom, e imprime a la resolución del dispositivo en lugar de al DPI que pareció suficiente en el momento de la compilación

Diagrama de una sola importación de SVG que crea un único Form XObject de PDF, el cual DrawSVGFormXObject coloca en muchas páginas con distintos tamaños, en comparación con cientos de copias de mapas de bits rasterizados
Una importación escribe el arte una sola vez como un Form XObject con sus propios recursos, y cada colocación es una invocación pequeña que no agrega bytes duplicados

¿Por qué usar un Form XObject en lugar de dibujar el SVG cada vez?

Un Form XObject es el mecanismo propio de PDF para contenido reutilizable: un flujo de contenido autocontenido con su propio cuadro delimitador y su propio diccionario de recursos, invocado por nombre. Importar un SVG a uno le da tres propiedades que el dibujo directo no ofrece. El arte existe una sola vez en el archivo. Cada colocación es una pequeña matriz y una invocación. Y las formas siguen siendo vectores, de modo que un visor con 800 % de zoom dibuja curvas, no píxeles

El detalle sutil está en los recursos. Las fuentes y los estados gráficos de un formulario deben vivir en el propio diccionario /Resources del formulario, no en los recursos de la página que resultara estar activa en el momento de la importación. HotPDF escribe las fuentes Base 14 y las entradas ExtGState que necesita un formulario dentro del propio formulario, porque un formulario que toma prestado un nombre de su primera página anfitriona se renderiza correctamente allí, pero produce un nombre de recurso sin resolver en cualquier otra página donde se coloque después

Dos inversiones: una para la geometría y otra para los glifos

El sistema de coordenadas de SVG crece hacia abajo desde la esquina superior izquierda; un Form XObject de PDF usa el espacio de usuario de PDF, que crece hacia arriba desde la esquina inferior izquierda. La transformación del viewBox raíz realiza una inversión del eje Y para que la geometría quede colocada correctamente. El texto necesita entonces una segunda inversión local, porque un glifo dibujado dentro de un marco ya invertido quedaría al revés

Esta es la misma corrección que aparece en sentido inverso cuando una página PDF se exporta como SVG, descrita en exportar páginas PDF a SVG. Un error aquí es fácil de detectar en arte con texto y casi invisible en arte sin texto, razón por la cual un logotipo con eslogan es una prueba de humo mejor que un logotipo solo

Diagrama de las dos inversiones del eje Y entre las coordenadas de SVG y PDF: la transformación del viewBox raíz corrige la geometría y los glifos de texto necesitan una segunda inversión local para mantenerse en posición correcta
La transformación del viewBox raíz invierte el eje Y una vez para que la geometría quede ubicada en el espacio de PDF, y el texto recibe una segunda inversión local, o cada glifo llega al revés
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');

    // Una importación, muchas colocaciones: los bytes del arte existen una sola 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;

¿Cómo sobreviven los hipervínculos al proceso?

No pueden sobrevivir dentro del formulario, porque un Form XObject es contenido y los destinos de enlace en PDF son anotaciones, que pertenecen a una página. Por lo tanto, un elemento <a> del SVG de origen no puede simplemente copiarse al flujo de contenido del formulario

HotPDF resuelve esto conservando los rectángulos de enlace como metadatos en el espacio del formulario durante el análisis, y materializándolos como anotaciones de enlace a nivel de página en cada colocación. DrawSVGFormXObject mapea esos rectángulos a través del rectángulo destino que usted pasa, de modo que un formulario colocado dos veces con tamaños distintos obtiene dos conjuntos de áreas activas con el tamaño correcto. Los rectángulos de enlace se mapean únicamente a través del tamaño raíz y nunca entran al flujo de contenido, lo cual mantiene el arte dibujado idéntico sin importar cuántos enlaces contenga. Pase AddLinks = False cuando quiera el arte sin la interactividad, por ejemplo en una página solo para impresión

Qué rechaza el importador, y por qué ese es el punto

SVG es un dialecto de XML con scripting, referencias externas y expansión de entidades, lo cual hace que un archivo SVG arbitrario sea una entrada tan hostil como lo sería un archivo HTML arbitrario. El perfil de importación es fail-closed (falla de forma segura) ante todo lo que se extienda fuera del documento: referencias a recursos externos, scripts, atributos de manejo de eventos, DTD y cualquier url() que no sea una referencia a un fragmento local

Fail-closed significa que la importación falla en lugar de descartar en silencio la construcción problemática. Esa distinción importa en un pipeline por lotes, porque un descarte silencioso produce un documento al que le falta algo que nadie sabe que falta, mientras que un rechazo produce un archivo que se puede poner en cuarentena e inspeccionar. Si usted acepta cargas de SVG provenientes de usuarios, este es el comportamiento que quiere, y se complementa con el aislamiento a nivel de proceso descrito en aislamiento de procesos de códec para imágenes no confiables

Diagrama de un perfil de importación de SVG fail-closed que acepta funciones vectoriales locales y rechaza scripts, referencias externas y DTD al hacer fallar la importación en lugar de descartarlos en silencio
El perfil de importación rechaza scripts, referencias externas y DTD al fallar por completo, lo cual le da un archivo para poner en cuarentena en lugar de un documento silenciosamente incompleto

Hay un límite interno que vale la pena conocer si usted también renderiza fuentes a color. El subconjunto estricto de SVG usado para los glifos de color OpenType SVG pasa por un punto de entrada separado y conserva su comportamiento de falla original, más restringido. La importación de arte general es una ruta distinta, de modo que añadir soporte de arcos, transparencia y CSS al arte importado no amplió lo que un glifo de fuente puede contener

Degradados, máscaras y la regla de no rasterizar

Los degradados se importan como bandas de color vectoriales acotadas por la ruta de recorte de la geometría original, no como una imagen rasterizada. Las máscaras usan el mismo enfoque: el contorno de la máscara se convierte en una ruta de recorte vectorial. El resultado es que una ilustración cargada de degradados se mantiene independiente de la resolución y se mantiene pequeña, mientras que un importador que rasterizara produciría un objeto de imagen grande cuya calidad queda fija en el momento de la importación

La contrapartida es la fidelidad en los casos extremos. Una aproximación por bandas de un degradado suave no es matemáticamente idéntica al degradado SVG, y el arte con docenas de máscaras suaves superpuestas se verá más cercano al original si en su lugar se renderiza como una imagen de alta resolución. Para conocer los modelos de sombreado que PDF admite de forma nativa, y cuándo recurrir a ellos directamente, consulte renderizar sombreados axiales y radiales

// Leer las estadísticas de importación le indica qué se procesó realmente
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 el mismo formulario en dos tamaños produce dos conjuntos de enlaces correctos
Pdf.CurrentPage.DrawSVGFormXObject(FormName, 40, 700, 240, 80);
Pdf.CurrentPage.DrawSVGFormXObject(FormName, 320, 700, 120, 40);

// Colocación solo para impresión: arte sin las áreas activas
Pdf.CurrentPage.DrawSVGFormXObject(FormName, 40, 60, 120, 40, False);

Dónde encaja la importación de SVG junto a las demás rutas vectoriales

Existen tres rutas de importación vectorial y no son intercambiables. La importación de SVG es adecuada para arte producido por herramientas de diseño y para todo lo que llega desde un pipeline web. La importación de metarchivos, cubierta en importación vectorial de EMF y WMF, es adecuada para contenido generado por aplicaciones de Windows y por componentes de gráficos que dibujan sobre un contexto de dispositivo. El dibujo directo mediante la API de página es adecuado para arte que su propio código calcula, donde no existe ningún formato intermedio

El argumento de la reutilización favorece a la importación de SVG específicamente para elementos repetidos. Los membretes, logotipos, marcas de agua, sellos de certificación y líneas decorativas se dibujan de forma idéntica en muchas páginas, y cada uno de ellos es un solo objeto de formulario más una invocación por página. Mida la diferencia de tamaño de archivo en un documento real antes de decidir que no importa: en un catálogo de 500 páginas con un logotipo vectorial por página, la diferencia entre un formulario y 500 copias inline suele ser de varios megabytes

La importación de SVG, la importación de metarchivos y el dibujo vectorial directo comparten el mismo escritor de flujo de contenido para Delphi y C++Builder; la lista completa de funciones está en la página del componente PDF para Delphi de HotPDF