Artículo técnico

Exportar páginas PDF a SVG en Delphi con HotPDF

HotPDF exporta una página de cualquier documento PDF cargado a marcado SVG independiente con una sola llamada, BuildLoadedPageSVG, que devuelve el documento SVG completo como una cadena. El marcado exportado conserva la geometría de la página, el texto como elementos SVG text reales, las imágenes ráster incrustadas y el estado de trazo que los operadores PDF habían establecido en cada operación de dibujo

Ese último aspecto es donde la mayoría de los conversores caseros fallan silenciosamente. Convertir una página PDF en SVG parece un problema de coordenadas, pero en realidad es un problema de estado. PDF es una máquina de pila cuyo estado gráfico cambia a medida que se interpreta el flujo de contenido; SVG es un árbol declarativo cuyos elementos llevan cada uno sus propios atributos de presentación. Todo lo que el intérprete no capture en el momento en que se emite un elemento simplemente desaparece de la salida, y el fallo es silencioso: se obtiene un SVG válido que renderiza una página sutilmente incorrecta

¿Por qué no se convierte una página PDF directamente en SVG?

Tres discrepancias hacen que la conversión no sea trivial, y las tres producen una salida que parece plausible hasta que se compara lado a lado con el original. La primera es el eje Y. El espacio de usuario de PDF crece hacia arriba desde la esquina inferior izquierda de la página; SVG crece hacia abajo desde la esquina superior izquierda. Un único volteo a nivel de página corrige las coordenadas de dibujo, pero luego rompe cada glifo, porque voltear todo el lienzo también invierte las formas de las letras

La segunda discrepancia es la herencia. En PDF, q y Q apilan y desapilan un estado gráfico que incluye el ancho de línea, el remate de línea, la unión de línea, el límite de inglete, el patrón de guiones, la fase de guiones y el alfa. En SVG, un elemento que no especifica un atributo lo hereda de un grupo ancestro, lo cual es una regla de alcance completamente distinta. Un exportador que solo rastrea la matriz de transformación actual y olvida el estado de trazo deja que el estado restaurado tras una Q se filtre en los elementos siguientes

La tercera es que PDF expresa varias cosas por convención en lugar de por valor. Los remates y las uniones de línea son números enteros, un ancho de línea cero significa un trazo de un píxel en el espacio del dispositivo en lugar de una línea invisible, y las variantes con asterisco de los operadores de pintura cambian la regla de relleno en lugar del color. Cada uno de esos casos necesita una traducción, no una copia

Una llamada para el caso habitual

Para la tarea habitual de exportar páginas para un visor web, una herramienta de comparación o una entrega de diseño, la superficie de la API se reduce a una sola función. BuildLoadedPageSVG recibe un índice de página basado en cero sobre el documento actualmente cargado y devuelve el documento SVG como un 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 devuelve el 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;

La misma exportación está disponible en la herramienta de línea de comandos de HotPDF como su comando export-svg, útil en canalizaciones de compilación y en scripts de regresión donde se desea una representación de una página comparable como texto sin escribir ni una línea de Pascal. Como el SVG es texto, resulta un complemento natural de la vía de rasterización descrita en renderizar una página PDF a un mapa de bits: el mapa de bits indica el aspecto de la página, el SVG indica de qué está hecha

¿Cómo se traslada el texto de PDF a los elementos de texto de SVG?

HotPDF compone la cadena de matriz de texto como prefijo por CTM por matriz de texto por volteo de glifo, donde el volteo de glifo es una multiplicación por la derecha por matrix(1,0,0,-1,0,0). Ese factor del lado derecho existe únicamente para anular el volteo vertical a nivel de página en las formas de los glifos, ya que un texto SVG dibujado en un marco local volteado aparecería invertido. Colocar la corrección en la matriz en lugar de en código con casos especiales hace que el texto rotado, reflejado o inclinado salga correcto sin ramificaciones adicionales

El posicionamiento horizontal usa la sintaxis de múltiples valores del atributo x del elemento SVG text, una coordenada por carácter, acumulada a partir de cada avance de glifo más el espaciado de caracteres Tc y el espaciado de palabras Tw vigentes en ese momento. El escalado horizontal Tz se incorpora a las columnas a y c de la matriz de texto en lugar de emitirse por separado, de modo que un visor que ignore atributos de texto poco habituales sigue colocando cada glifo donde lo situó el PDF. El texto producido mediante composición tipográfica compleja, tratado en composición de texto en escrituras complejas, sigue el mismo camino, porque el compositor ya ha resuelto los clústeres en glifos posicionados en el momento en que se interpreta el flujo de contenido

Rotación e imágenes: dos volteos fáciles de invertir por error

Una página con una entrada /Rotate distinta de cero necesita una pretransformación compuesta por un volteo respecto a la altura del lienzo rotado y una rotación expresada en un espacio de visualización con el eje Y hacia arriba. Las tres matrices de rotación son (0,-1,1,0,0,W) para 90 grados, (-1,0,0,-1,W,H) para 180 y (0,1,-1,0,H,0) para 270, siendo W y H las dimensiones de la página antes de la rotación. Deducirlas a mano invita a errores de signo en exactamente tres puntos, por lo que el exportador las compone mediante la misma rutina de multiplicación de matrices que gestiona el resto de las transformaciones

Las imágenes incrustadas necesitan su propio volteo, porque el espacio de imagen de PDF coloca la primera fila de muestras en el borde superior del cuadrado unitario, mientras que el elemento SVG image lleva un marco local con el eje Y hacia abajo. Por eso la transformación emitida es el CTM multiplicado por la derecha por matrix(1,0,0,-1,0,1). Un error aquí produce fotografías invertidas verticalmente en una página por lo demás perfecta, el tipo de defecto que un revisor detecta al instante y que una prueba automatizada a menudo no detecta

¿Qué conserva realmente el dispositivo de estado gráfico?

HotPDF distribuye los operadores de estado de trazo w, J, j, M y d a través de una interfaz de dispositivo opcional independiente, de modo que la fidelidad del trazo se añadió sin modificar la vtable del dispositivo de contenido existente y sin romper la compatibilidad binaria del código compilado contra versiones anteriores. En concreto, el SVG exportado recibe palabras clave traducidas en lugar de enteros PDF sin procesar:

// Las enumeraciones enteras de PDF pasan a ser atributos de palabra clave SVG
//   remate de línea  0, 1, 2  ->  butt, round, square
//   unión de línea 0, 1, 2  ->  miter, round, bevel
//
// Un ancho de línea cero significa un trazo de un píxel en el espacio del
// dispositivo en PDF, así que el exportador emite vector-effect="non-scaling-stroke"
// para mantener el trazo visible y con un ancho cercano a un píxel de dispositivo
// tras aplicar el CTM
//
// f* B* b* seleccionan la regla par-impar y emiten fill-rule="evenodd",
// mientras que f B b mantienen el valor predeterminado de SVG de bobinado distinto de cero

Restaurar el estado en una Q cubre a la vez la opacidad, el ancho de línea, el remate, la unión, el límite de inglete, el patrón de guiones y la fase de guiones. Los XObjects de formulario anidados capturan y restauran ese mismo conjunto completo en sus límites, de modo que un borde discontinuo definido dentro de un sello no puede filtrar su patrón al contenido de página que sigue. Si ya se rastrea el comportamiento de recorte y CTM por otros motivos, este es el mismo modelo de estado que aparece en la importación de vectores EMF y WMF, funcionando en sentido inverso

Límites que conviene conocer antes de publicarlo

El exportador es honesto sobre su alcance, y conocer sus límites de antemano sale más barato que descubrirlos en producción. El color llega al dispositivo SVG a través de los operadores rg, RG, g y G. Los rellenos establecidos mediante un espacio de color más scn, que es como se pintan los colores Separation, DeviceN e ICCBased, no llegan al dispositivo como una terna RGB resuelta, de modo que las páginas que usan colores planos de este modo exportan su geometría pero no esos colores. Para fuentes orientadas a impresión, conviene rasterizar en su lugar o aplanar antes los colores planos; el propio modelo de pintura se trata en el renderizado de colores planos Separation y DeviceN

Dos notas menores ahorran tiempo de depuración. Los literales de color hexadecimales se emiten en mayúsculas, de modo que una prueba que compara con #ff0000 falla frente a un #FF0000 perfectamente correcto. Y el dispositivo SVG se cuenta por referencias a través de su interfaz, lo que significa que liberarlo consiste en dejar que la interfaz salga de ámbito en lugar de llamar a Free sobre el objeto, una distinción que importa si se extiende el dispositivo para emitir marcado propio junto al contenido de la página

La exportación a SVG combina de forma natural con la comparación estructural cuando se necesita saber si un documento generado realmente cambió entre dos compilaciones. El conjunto de herramientas más amplio para documentos cargados, desde el renderizado hasta la edición y la exportación, está documentado en la página del componente PDF para Delphi de HotPDF