Un logo EPS colocado en una página de PDF puede salir con su contorno desaparecido, una marca de un solo color impresa como su propio negativo, o Æ convertido en á. Cada síntoma señala un lugar donde la semántica de gráficos de PostScript y PDF se separan, así que la importación EPS de PDFlibPas para Delphi interpreta el programa PostScript y escribe cada diferencia como operadores PDF nativos
Interpretar el programa o pasarlo tal cual
PostScript puede vivir dentro de un PDF de dos maneras, y solo una de ellas muestra algo en pantalla. ISO 32000-1 §8.8.2 define un PostScript XObject, un stream /Subtype /PS cuyos bytes se copian, sin interpretar, al trabajo de impresión solo cuando el dispositivo de salida es una impresora PostScript; en cualquier otro lugar no tiene efecto, y la descripción general de §8.8 dice que los PostScript XObjects no deben usarse. PDF/A los excluye sin más. TPDFlib.NewPostScriptXObject y DrawPostScriptXObject escriben esa forma de paso directo, mientras que ImportEPSFromFile y DrawPostScript corren el programa por un intérprete y emiten caminos ordinarios, imágenes inline y texto. Mande el artwork por el intérprete, y reserve un PS XObject para un fragmento dirigido al RIP de una sala de impresión concreta, el raster image processor que va delante de una prensa o de una impresora de producción. Interpretar EPS es también un trabajo distinto de la importación vectorial de EMF, donde la entrada es una lista de records GDI para reproducir; un archivo EPS es un programa
procedure AddPrintRoomHook(PDF: TPDFlib);
var
PSRef: Integer;
begin
// Se guarda verbatim como un stream /Subtype /PS y se referencia con Do.
// Solo se ejecuta en una impresora PostScript; los visores no dibujan nada
PSRef := PDF.NewPostScriptXObject(
'userdict /PrintRoomMark known { PrintRoomMark } if');
if PSRef <> 0 then
PDF.DrawPostScriptXObject(PSRef);
end;
| Comportamiento de PostScript | Comportamiento de PDF | Resultado de una traducción literal |
|---|---|---|
gsave / grestore guardan y restauran el camino actual y el punto actual | q / Q dejan el camino en paz, y pintar lo termina (§8.5.1) | Un contorno o un segmento que continúa después de grestore desaparece |
clip conserva el camino actual para pintarlo después | A W lo sigue un operador de pintura, por lo general n, que termina el camino | Un trazo después del clip no tiene nada que dibujar |
ImageMatrix mapea el espacio de usuario al espacio de imagen | Toda imagen llena el cuadrado unitario, primera fila arriba (§8.9.4) | Las imágenes bottom-up o rotadas aterrizan volteadas o fuera de sitio |
true imagemask pinta los bits 1 | Un stencil con el Decode [0 1] por defecto pinta los bits 0 (§8.9.6.2) | Un logo de un solo color se imprime como su negativo |
| Las fuentes Roman Standard-14 usan StandardEncoding por defecto | El nombre StandardEncoding no tiene significado especial (§9.6.6.1) | Acentos, comillas y ligaduras cambian letras |
Los comentarios %%BoundingBox y %%HiResBoundingBox definen el área del artwork | La colocación es la matriz que el writer emita | El artwork se corre o se escala fuera de su caja prevista |
¿Adónde fue a parar el contorno después de grestore?
El contorno se esfumó porque PDF jamás guardó el camino. ISO 32000-1 §8.5.1 lo dice en una frase: "En PDF (a diferencia de PostScript), el camino actual no es parte del estado de gráficos", y después de que un camino se pinta no hay camino actual hasta que m o re empiece uno nuevo. Los productores PostScript dependen de la regla contraria. La manera usual de dibujar una forma rellena con contorno es gsave fill grestore stroke: el relleno consume una copia guardada del camino, grestore lo devuelve, y el trazo pinta la misma geometría. Mapee gsave a q, fill a f y grestore a Q, y el trazo llega con un camino vacío, así que la forma queda rellena y el contorno se fue. Un lineto después de grestore que continúa el camino restaurado falla de la misma manera, como un segmento faltante
uses
SysUtils, PDFlibrary;
const
// Triángulo relleno con contorno: el relleno corre dentro de gsave/grestore,
// y el trazo después de grestore reutiliza el camino restaurado
TriangleEPS =
'%!PS-Adobe-3.0 EPSF-3.0'#10 +
'%%BoundingBox: 0 0 200 120'#10 +
'newpath 20 20 moveto 180 20 lineto 100 100 lineto closepath'#10 +
'gsave 0.85 setgray fill grestore'#10 +
'3 setlinewidth 0 setgray stroke'#10;
var
PDF: TPDFlib;
begin
PDF := TPDFlib.Create;
try
PDF.SetOrigin(1); // Left y Top medidos desde la esquina superior izquierda
// Width y Height en 0 conservan el tamaño del bounding box de 200 x 120
if PDF.DrawPostScript(72, 72, 0, 0, TriangleEPS) = 1 then
PDF.SaveToFile('triangle.pdf')
else
Writeln('DrawPostScript failed, code ', PDF.LastErrorCode);
// Esperado: un triángulo gris claro con un contorno negro de 3 unidades.
// Una traducción literal q/f/Q/S rellena el triángulo y pierde el contorno
finally
PDF.Free;
end;
end.
PDFlibPas conserva su propio camino PostScript, aparte de lo que el stream de contenido del PDF tenga en el momento, y escribe el camino PDF solo cuando algo pinta o recorta. Cada punto queda fijado bajo la matriz de transformación vigente cuando corrieron moveto o lineto, como en PostScript, donde un scale o un concat posterior jamás mueve puntos ya puestos en el camino. currentpoint mapea el punto guardado de vuelta a través de la matriz actual, así que responde en el espacio de usuario presente. Un gsave registra una referencia al final de la cadena de caminos más el punto actual, la matriz, la fuente y el espacio de color, así que los guardados anidados jamás copian caminos enteros
El recorte es donde los dos modelos de camino se vuelven a separar, y la importación sigue ahí también a PostScript: clip y eoclip emiten W n para el lado PDF pero dejan el camino lógico en su lugar para el trazo que suele venir después, mientras que rectclip, que arranca un camino vacío nuevo en PostScript, emite re W n y lo limpia. El almacenamiento de caminos tiene un tope de 100,000 nodos, y las repeticiones dibujan contra el presupuesto de operaciones compartido, así que un archivo hostil no puede ciclar gsave y stroke para inflar la salida
Colocar imágenes: ImageMatrix apunta al revés
Una imagen EPS aterriza volteada cuando su ImageMatrix se trata como colocación, porque describe el mapeo contrario. En PDF, §8.9.4 ata el borde de la imagen al cuadrado unitario del espacio de usuario con la fila de muestras superior arriba, una matriz implícita imagen-a-usuario de [1/w 0 0 -1/h 0 1]. El ImageMatrix de PostScript mapea el espacio de usuario al espacio de imagen. La matriz de colocación de PDF es por lo tanto [w 0 0 -h 0 h] multiplicada por la inversa del ImageMatrix. Para la matriz común de arriba hacia abajo [w 0 0 -h 0 h] el producto es la identidad. Por eso los convertidores ingenuos se ven bien en el uso casual. Un escaneo bottom-up escrito con [w 0 0 h 0 0] da [1 0 0 -1 0 1], un volteo vertical dentro del cuadrado unitario, y las matrices rotadas o sesgadas no necesitan caso especial. Cuando una fuente de datos finita termina antes de tiempo, PDFlibPas pinta solo las filas completas, conserva el ImageMatrix original y escala por las filas que realmente llegaron, así que una imagen truncada llena la banda superior de su área a la escala correcta; estirar las filas sobrevivientes sobre la altura completa produciría una imagen distorsionada que se ve intacta
¿Qué bit pinta imagemask?
Con true como operando de polaridad, imagemask pinta los bits 1, y en PDF eso es Decode [1 0]. ISO 32000-1 §8.9.6.2 define [0 1] como el default de una image mask, o sea que una muestra 0 marca la página con el color actual y una muestra 1 deja el fondo en paz, y [1 0] lo revierte. El booleano de PostScript dice qué valor de muestra pinta. Léalo como "invierte el default de PDF" y el logo se imprime como un negativo, un bloque sólido con la marca recortada de él. PDFlibPas mapea true a [1 0] y false a [0 1], emite el stencil como una imagen inline con /IM true para que pinte en el color que el programa haya fijado, y en la forma de diccionario acepta solo esos dos arreglos Decode
Las muestras de doce bits esconden un desajuste más pequeño. PDF no tiene imágenes de 12 bits; §8.9.5.1 permite 1, 2, 4, 8 y, desde PDF 1.5, 16 bits por componente. Correr cada muestra cuatro bits a la izquierda la mete en 16 bits sin perder nada, pero el valor más grande se vuelve 65520, no 65535, así que con el Decode por defecto cada muestra brillante sale unos 0.023 por ciento demasiado oscura. La importación conserva el corrimiento y estira el extremo superior del Decode por 65535/65520, lo que restaura cada valor normalizado. Una imagen de 12 bits eleva la versión de salida a PDF 1.5 automáticamente; en modo PDF/A o PDF/X, o cuando la versión de salida está bloqueada por debajo de 1.5, la importación se detiene con el error 538 antes de que la página cambie
¿Por qué Æ sale como á?
Una fuente Roman en PostScript usa StandardEncoding salvo que el programa la recodifique, y PDF no puede nombrar esa tabla: ISO 32000-1 §9.6.6.1 no le da significado especial al nombre, dejando WinAnsiEncoding, MacRomanEncoding y MacExpertEncoding como las opciones predefinidas. Las tablas del Anexo D.2 muestran qué sale mal cuando los códigos se le entregan a WinAnsi sin cambios: el octal 341 (0xE1) es AE en StandardEncoding y aacute en WinAnsi, así que Æ se vuelve á; 0xAE es la ligadura fi en uno y ® en el otro; 0x27 es quoteright en StandardEncoding y un quotesingle recto en WinAnsi, lo que convierte apóstrofos tipográficos en de máquina de escribir. Para cada fuente Roman Standard-14 que use un programa, PDFlibPas escribe un recurso de fuente Type 1 privado cuyo diccionario /Encoding lleva /BaseEncoding /WinAnsiEncoding y un arreglo /Differences que nombra los 256 códigos en orden StandardEncoding, así que ningún código depende de cómo llene su tabla base el visor. Los anchos vienen de la misma codificación, un mapa ToUnicode deja que copiar y buscar devuelvan Æ, y Symbol y ZapfDingbats conservan sus códigos incorporados. La fuente que su código seleccionó antes de la importación queda intacta, porque recodificar una fuente en caché para acomodar al EPS cambiaría texto ya colocado con ella
Esos recursos nombran las fuentes sin incrustarlas, lo que choca con los perfiles que exigen incrustación. En un documento configurado para PDF/A, PDF/X o PDF/UA, o después de SetEmbedAllFonts(1), el texto EPS falla con el error 538 y la página queda como estaba. Para salida de archivo la ruta más limpia es un EPS exportado con el texto convertido a contornos, que se importa como caminos planos en cualquier modo; las fuentes que ya quedaron sin incrustar en un archivo existente necesitan incrustar las fuentes faltantes para PDF/A en cambio. El texto EPS mapea a las 14 fuentes estándar por sus nombres exactos; el artwork que incrusta su propio programa de fuentes se importa limpio una vez que su texto está en contornos, lo que además evita que una fuente sustituta local corra cada línea
Bounding boxes, comentarios DSC y archivos EPS binarios
La colocación escala el bounding box del artwork al rectángulo que usted pasa, así que la caja tiene que venir del lugar correcto. PDFlibPas lee %%BoundingBox y %%HiResBoundingBox solo de líneas de comentario DSC reales, o sea comentarios de nivel superior que empiezan una línea, y una caja de alta resolución válida le gana a la entera. Una búsqueda de substring también encuentra esas keywords dentro de strings literales, cuerpos de procedimientos y datos binarios de imagen. Los archivos EPS cargan las tres cosas. Un valor de header (atend) difiere los números a un comentario del trailer, leído cuando el escáner lo alcanza; un archivo sin ninguna caja recurre a 612 por 792, y una caja vacía o invertida falla con el error 124. Width y Height escalan la caja de manera independiente: pase 0 para ambos para conservar el tamaño natural, o pase ambos en la relación de aspecto de la caja, porque un solo valor distinto de cero estira el artwork por un solo eje
Los datos binarios deciden el punto de entrada. DrawPostScript toma un WideString, fino para PostScript construido en código, pero las muestras crudas de imagen, los datos Flate leídos a través de currentfile y el envoltorio DOS EPS contienen bytes que no sobreviven un viaje por texto Unicode. ImportEPSFromFile lee el archivo como bytes crudos, reconoce el header de vista previa C5 D0 D3 C6, verifica que el offset y la longitud del segmento PostScript caigan dentro del archivo, y ejecuta solo ese segmento mientras ignora la vista previa TIFF o WMF. Ambas llamadas toman a lo sumo 4 MiB de fuente, exigen Options = 0, devuelven 1 o 0, y anexan o el dibujo completo o nada. La razón del fallo vive en LastErrorCode, que se reinicia al leerse, así que captúrelo una sola vez
uses
SysUtils, PDFlibrary;
function PlaceEPS(PDF: TPDFlib; const FileName: string;
Left, Top: Double): Boolean;
var
Code: Integer;
begin
// 0, 0: tamaño natural desde %%HiResBoundingBox o %%BoundingBox
Result := PDF.ImportEPSFromFile(Left, Top, 0, 0, FileName) = 1;
if Result then
Exit;
Code := PDF.LastErrorCode; // leerlo lo limpia, así que léalo una sola vez
case Code of
402:
Writeln(FileName, ': file cannot be opened');
PDFLIB_ERROR_POSTSCRIPT_INVALID: // 124
Writeln(FileName, ': malformed PostScript, bad bounding box or nothing painted');
PDFLIB_ERROR_POSTSCRIPT_UNSUPPORTED: // 538
Writeln(FileName, ': construct the import does not handle, or Standard-14 text in an archival profile');
PDFLIB_ERROR_POSTSCRIPT_BUDGET: // 539
Writeln(FileName, ': source over 4 MiB or a work limit reached');
else
Writeln(FileName, ': import failed with code ', Code);
end;
end;
var
PDF: TPDFlib;
begin
PDF := TPDFlib.Create;
try
PDF.SetOrigin(1);
if PlaceEPS(PDF, 'logo.eps', 40, 40) then
PDF.SaveToFile('letterhead.pdf');
finally
PDF.Free;
end;
end.
Cuando el error 538 nombre un operador, reexporte el artwork desde su editor como EPS plano con el texto convertido a contornos: eso suele ser más rápido que perseguir al operador, y el artwork en contornos también se coloca en un documento PDF/A o PDF/UA construido con la PDFlibPas Delphi PDF Library sin el conflicto de fuentes Standard-14