Un logo EPS posé sur une page PDF peut sortir avec son contour manquant, une marque monochrome imprimée en son propre négatif, ou Æ transformé en á. Chaque symptôme marque un endroit où la sémantique graphique de PostScript et celle de PDF ne sont pas d'accord, si bien que l'import EPS de PDFlibPas pour Delphi interprète le programme PostScript et écrit chaque différence en opérateurs PDF natifs
Interpréter le programme ou le passer tel quel
Le PostScript peut se loger à l'intérieur d'un PDF de deux façons, et une seule montre quelque chose à l'écran. ISO 32000-1 §8.8.2 définit un PostScript XObject, un flux /Subtype /PS dont les octets sont copiés, sans interprétation, dans le travail d'impression seulement quand le périphérique de sortie est une imprimante PostScript ; partout ailleurs il n'a aucun effet, et la vue d'ensemble de la §8.8 dit que les PostScript XObjects ne doivent pas être utilisés. Le PDF/A les exclut carrément. TPDFlib.NewPostScriptXObject et DrawPostScriptXObject écrivent cette forme de passage direct, tandis que ImportEPSFromFile et DrawPostScript font passer le programme au travers d'un interpréteur et émettent des chemins ordinaires, des images en ligne et du texte. Envoyez le dessin au travers de l'interpréteur, et gardez un PS XObject pour un fragment destiné au RIP d'un atelier d'impression, le raster image processor devant une presse ou une imprimante de production. Interpréter un EPS est aussi un autre métier que l'import vectoriel EMF, où l'entrée est une liste d'enregistrements GDI à rejouer ; un fichier EPS est un programme
procedure AddPrintRoomHook(PDF: TPDFlib);
var
PSRef: Integer;
begin
// Stocké verbatim comme flux /Subtype /PS et référencé avec Do.
// Il ne s'exécute que sur une imprimante PostScript ; les visionneurs ne dessinent rien
PSRef := PDF.NewPostScriptXObject(
'userdict /PrintRoomMark known { PrintRoomMark } if');
if PSRef <> 0 then
PDF.DrawPostScriptXObject(PSRef);
end;
| Comportement PostScript | Comportement PDF | Résultat d'une traduction littérale |
|---|---|---|
gsave / grestore sauvegardent et restaurent le chemin courant et le point courant | q / Q laissent le chemin tranquille, et la peinture le termine (§8.5.1) | Un contour ou un segment en cours après grestore disparaît |
clip garde le chemin courant pour une peinture ultérieure | W est suivi d'un opérateur de peinture, d'ordinaire n, qui termine le chemin | Un contour après le clip n'a rien à tracer |
ImageMatrix mappe l'espace utilisateur vers l'espace image | Chaque image remplit le carré unitaire, première rangée en haut (§8.9.4) | Les images de bas en haut ou tournées arrivent retournées ou mal placées |
true imagemask peint les bits à 1 | Un stencil avec le Decode [0 1] par défaut peint les bits à 0 (§8.9.6.2) | Un logo monochrome s'imprime en son négatif |
| Les polices Roman Standard-14 ont StandardEncoding par défaut | Le nom StandardEncoding n'a pas de sens particulier (§9.6.6.1) | Accents, guillemets et ligatures changent de lettres |
Les commentaires %%BoundingBox et %%HiResBoundingBox définissent la zone du dessin | Le placement est la matrice que l'écrivain émet | Le dessin se décale ou change d'échelle hors de sa boîte prévue |
Où le contour est-il passé après grestore ?
Le contour a disparu parce que PDF n'a jamais sauvegardé le chemin. ISO 32000-1 §8.5.1 le dit en une phrase : « En PDF (contrairement à PostScript), le chemin courant ne fait pas partie de l'état graphique », et après la peinture d'un chemin il n'y a plus de chemin courant jusqu'à ce qu'un m ou un re en commence un nouveau. Les producteurs PostScript comptent sur la règle opposée. La manière habituelle de dessiner une forme remplie avec un contour est gsave fill grestore stroke : le remplissage consomme une copie sauvegardée du chemin, grestore rapporte le chemin, et le contour peint la même géométrie. Mappez gsave sur q, fill sur f et grestore sur Q, et le contour arrive avec un chemin vide, si bien que la forme est remplie et le contour envolé. Un lineto après grestore qui continue le chemin restauré échoue de la même façon, en segment manquant
uses
SysUtils, PDFlibrary;
const
// Triangle rempli avec un contour : le remplissage tourne dans gsave/grestore,
// et le contour après grestore réutilise le chemin restauré
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 et Top mesurés depuis le coin en haut à gauche
// Width et Height de 0 gardent la taille de boîte englobante 200 x 120
if PDF.DrawPostScript(72, 72, 0, 0, TriangleEPS) = 1 then
PDF.SaveToFile('triangle.pdf')
else
Writeln('DrawPostScript failed, code ', PDF.LastErrorCode);
// Attendu : un triangle gris clair avec un contour noir de 3 unités.
// Une traduction littérale q/f/Q/S remplit le triangle et perd le contour
finally
PDF.Free;
end;
end.
PDFlibPas garde son propre chemin PostScript, séparé de ce que le flux de contenu PDF porte à l'instant, et n'écrit le chemin PDF que quand quelque chose peint ou découpe. Chaque point est fixé sous la matrice de transformation en vigueur quand moveto ou lineto a tourné, comme en PostScript, où un scale ou un concat ultérieur ne déplace jamais les points déjà sur le chemin. currentpoint remappe le point stocké en arrière au travers de la matrice courante, si bien qu'il répond dans l'espace utilisateur présent. Un gsave enregistre une référence à la fin de la chaîne de chemins plus le point courant, la matrice, la police et l'espace de couleurs, si bien que des sauvegardes imbriquées ne copient jamais des chemins entiers
Le découpage est l'endroit où les deux modèles de chemin se séparent encore, et l'import suit PostScript là aussi : clip et eoclip émettent W n pour le côté PDF mais laissent le chemin logique en place pour le contour qui suit souvent, tandis que rectclip, qui commence un chemin vide neuf en PostScript, émet re W n et le vide. Le stockage de chemins est plafonné à 100 000 nœuds, et les rejeux tirent sur le budget d'opérations partagé, si bien qu'un fichier hostile ne peut pas boucler gsave et stroke pour gonfler la sortie
Placer les images : ImageMatrix pointe dans l'autre sens
Une image EPS arrive retournée quand son ImageMatrix est traité comme placement, car il décrit la correspondance opposée. En PDF, la §8.9.4 attache la bordure de l'image au carré unitaire de l'espace utilisateur avec la rangée d'échantillons supérieure en haut, une matrice implicite image vers utilisateur de [1/w 0 0 -1/h 0 1]. Le ImageMatrix de PostScript mappe l'espace utilisateur vers l'espace image. La matrice de placement PDF est donc [w 0 0 -h 0 h] multipliée par l'inverse de ImageMatrix. Pour la matrice courante de haut en bas [w 0 0 -h 0 h] le produit est l'identité. Voilà pourquoi les convertisseurs naïfs ont l'air corrects en usage courant. Un balayage de bas en haut écrit avec [w 0 0 h 0 0] donne [1 0 0 -1 0 1], un retournement vertical à l'intérieur du carré unitaire, et les matrices tournées ou inclinées ne demandent aucun cas particulier. Quand une source de données finie se termine tôt, PDFlibPas ne peint que les rangées complètes, garde le ImageMatrix original et met à l'échelle par les rangées réellement arrivées, si bien qu'une image tronquée remplit la bande supérieure de sa zone à la bonne échelle ; étirer les rangées survivantes sur toute la hauteur produirait une image déformée qui semble intacte
Quel bit l'imagemask peint-il ?
Avec true comme opérande de polarité, imagemask peint les bits à 1, et en PDF cela veut dire Decode [1 0]. ISO 32000-1 §8.9.6.2 définit [0 1] comme défaut d'un masque d'image, à savoir qu'un échantillon 0 marque la page avec la couleur courante et un 1 laisse l'arrière-plan tranquille, et [1 0] inverse cela. Le booléen PostScript dit quelle valeur d'échantillon peint. Le lire comme « inverse le défaut PDF » et le logo s'imprime en négatif, un bloc plein avec la marque découpée dedans. PDFlibPas mappe true sur [1 0] et false sur [0 1], émet le stencil comme image en ligne avec /IM true pour qu'il peigne dans la couleur que le programme a posée, et sous la forme dictionnaire n'accepte que ces deux tableaux Decode
Les échantillons de douze bits cachent un décalage plus petit. PDF ne connaît pas d'images 12 bits ; la §8.9.5.1 admet 1, 2, 4, 8 et, depuis PDF 1.5, 16 bits par composante. Décaler chaque échantillon de quatre bits vers la gauche le fait tenir dans 16 bits sans rien perdre, mais la plus grande valeur devient 65520, pas 65535, si bien qu'avec le Decode par défaut chaque échantillon clair sort environ 0.023 pour cent trop sombre. L'import garde le décalage et étire la borne haute du Decode par 65535/65520, ce qui restaure chaque valeur normalisée. Une image 12 bits monte la version de sortie à PDF 1.5 automatiquement ; en mode PDF/A ou PDF/X, ou quand la version de sortie est verrouillée sous 1.5, l'import s'arrête avec l'erreur 538 avant que la page ne change
Pourquoi Æ sort-il en á ?
Une police Roman en PostScript utilise StandardEncoding tant que le programme ne la réencode pas, et PDF ne peut pas nommer cette table : ISO 32000-1 §9.6.6.1 ne donne aucun sens particulier au nom, laissant WinAnsiEncoding, MacRomanEncoding et MacExpertEncoding comme choix prédéfinis. Les tables de l'annexe D.2 montrent ce qui tourne mal quand les codes sont remis à WinAnsi inchangés : l'octal 341 (0xE1) est AE en StandardEncoding et aacute en WinAnsi, si bien que Æ devient á ; 0xAE est la ligature fi dans l'un et ® dans l'autre ; 0x27 est quoteright en StandardEncoding et un quotesingle droit en WinAnsi, ce qui change les apostrophes typographiques en apostrophes de machine à écrire. Pour chaque police Roman Standard-14 qu'un programme utilise, PDFlibPas écrit une ressource de police Type 1 privée dont le dictionnaire /Encoding porte /BaseEncoding /WinAnsiEncoding et un tableau /Differences qui nomme les 256 codes dans l'ordre StandardEncoding, si bien qu'aucun code ne dépend de la façon dont un visionneur remplit sa table de base. Les largeurs viennent du même encodage, une table ToUnicode laisse copie et recherche rendre Æ, et Symbol et ZapfDingbats gardent leurs codes intégrés. La police que votre code avait sélectionnée avant l'import reste intacte, car réencoder une police en cache pour plaire à l'EPS changerait le texte déjà posé avec elle
Ces ressources nomment les polices sans les embarquer, ce qui se heurte aux profils qui exigent l'embarquement. Dans un document préparé pour PDF/A, PDF/X ou PDF/UA, ou après SetEmbedAllFonts(1), le texte EPS échoue avec l'erreur 538 et la page reste telle quelle. Pour une sortie d'archivage, la route plus propre est un EPS exporté avec le texte converti en contours, qui s'importe comme chemins plats dans n'importe quel mode ; les polices déjà laissées non embarquées dans un fichier existant demandent l'embarquement des polices manquantes pour le PDF/A à la place. Le texte EPS se mappe sur les 14 polices standard par leurs noms exacts ; un dessin qui embarque son propre programme de police s'importe proprement une fois son texte passé en contours, ce qui empêche aussi une police de substitution locale de déplacer chaque ligne
Boîtes englobantes, commentaires DSC et fichiers EPS binaires
Le placement met la boîte englobante du dessin à l'échelle dans le rectangle que vous passez, si bien que la boîte doit venir du bon endroit. PDFlibPas ne lit %%BoundingBox et %%HiResBoundingBox que dans de vraies lignes de commentaires DSC, c'est-à-dire des commentaires de premier niveau qui ouvrent une ligne, et une boîte haute résolution valide l'emporte sur la boîte entière. Une recherche de sous-chaîne naïve trouve aussi ces mots-clés à l'intérieur de chaînes littérales, de corps de procédures et de données d'image binaires. Les fichiers EPS portent les trois. Une valeur d'en-tête (atend) reporte les nombres à un commentaire dans le trailer, lu quand le scanner l'atteint ; un fichier sans aucune boîte retombe sur 612 par 792, et une boîte vide ou inversée échoue avec l'erreur 124. Width et Height mettent la boîte à l'échelle indépendamment : passez 0 pour les deux pour garder la taille naturelle, ou passez les deux dans le rapport de forme de la boîte, car une seule valeur non nulle étire le dessin le long d'un seul axe
Les données binaires décident du point d'entrée. DrawPostScript prend un WideString, très bien pour du PostScript construit dans le code, mais les échantillons d'images bruts, les données Flate lues au travers de currentfile et l'enveloppe DOS EPS contiennent des octets qui ne survivent pas à un voyage en texte Unicode. ImportEPSFromFile lit le fichier en octets bruts, reconnaît l'en-tête de prévisualisation C5 D0 D3 C6, vérifie que l'offset et la longueur du segment PostScript tombent à l'intérieur du fichier, et n'exécute que ce segment en ignorant la prévisualisation TIFF ou WMF. Les deux appels prennent au plus 4 Mio de source, exigent Options = 0, renvoient 1 ou 0, et ajoutent soit tout le dessin soit rien. La raison de l'échec se trouve dans LastErrorCode, qui se remet à zéro quand on le lit, donc capturez-le une fois
uses
SysUtils, PDFlibrary;
function PlaceEPS(PDF: TPDFlib; const FileName: string;
Left, Top: Double): Boolean;
var
Code: Integer;
begin
// 0, 0 : taille naturelle depuis %%HiResBoundingBox ou %%BoundingBox
Result := PDF.ImportEPSFromFile(Left, Top, 0, 0, FileName) = 1;
if Result then
Exit;
Code := PDF.LastErrorCode; // la lecture efface le code, donc le lire une fois
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.
Quand l'erreur 538 nomme un opérateur, réexportez le dessin depuis son éditeur en EPS simple avec le texte converti en contours : c'est d'ordinaire plus rapide que de courir après l'opérateur, et un dessin en contours se place aussi dans un document PDF/A ou PDF/UA construit avec la PDFlibPas Delphi PDF Library sans le conflit de polices Standard-14