Ein EPS-Logo, auf einer PDF-Seite platziert, kann ohne seine Kontur herauskommen, ein einfarbiges Zeichen als sein eigenes Negativ gedruckt, oder Æ in á verwandelt. Jedes Symptom markiert eine Stelle, wo die Grafik-Semantik von PostScript und PDF auseinandergeht, also deutet der PDFlibPas-EPS-Import für Delphi das PostScript-Programm aus und schreibt jeden Unterschied als native PDF-Operatoren heraus
Das Programm deuten oder durchreichen
PostScript kann auf zwei Arten in einem PDF sitzen, und nur eine davon zeigt irgendetwas auf dem Bildschirm. ISO 32000-1 §8.8.2 definiert ein PostScript XObject, einen /Subtype /PS-Stream, dessen Bytes uninterpretiert nur dann in den Druckjob kopiert werden, wenn das Ausgabegerät ein PostScript-Drucker ist; überall sonst hat es keine Wirkung, und die §8.8-Übersicht sagt, PostScript XObjects sollten nicht benutzt werden. PDF/A schließt sie rundheraus aus. TPDFlib.NewPostScriptXObject und DrawPostScriptXObject schreiben diese Durchreich-Form, während ImportEPSFromFile und DrawPostScript das Programm durch einen Interpreter laufen lassen und gewöhnliche Pfade, Inline-Bilder und Text emittieren. Schicken Sie Artwork durch den Interpreter, und heben Sie ein PS XObject für ein Fragment auf, das auf den RIP eines Printrooms zielt, den Raster Image Processor vor einer Druckmaschine oder einem Produktionsdrucker. EPS zu deuten ist auch eine andere Aufgabe als der EMF-Vektorimport, bei dem die Eingabe eine Liste von GDI-Records zum Abspielen ist; eine EPS-Datei ist ein Programm
procedure AddPrintRoomHook(PDF: TPDFlib);
var
PSRef: Integer;
begin
// Wortgetreu als /Subtype /PS-Stream gespeichert und mit Do referenziert.
// Es läuft nur auf einem PostScript-Drucker; Viewer zeichnen nichts
PSRef := PDF.NewPostScriptXObject(
'userdict /PrintRoomMark known { PrintRoomMark } if');
if PSRef <> 0 then
PDF.DrawPostScriptXObject(PSRef);
end;
| PostScript-Verhalten | PDF-Verhalten | Ergebnis einer wörtlichen Übersetzung |
|---|---|---|
gsave / grestore sichern den aktuellen Pfad und den aktuellen Punkt und stellen sie wieder her | q / Q lassen den Pfad in Ruhe, und Malen beendet ihn (§8.5.1) | Ein Umriss oder ein sich fortsetzendes Segment nach grestore verschwindet |
clip behält den aktuellen Pfad für späteres Malen | Auf W folgt ein Maloperator, meist n, der den Pfad beendet | Ein Stroke nach dem Clip hat nichts zu zeichnen |
ImageMatrix mappt den User Space in den Bildraum | Jedes Bild füllt das Einheitsquadrat, erste Zeile oben (§8.9.4) | Bottom-up- oder gedrehte Bilder landen gespiegelt oder am falschen Ort |
true imagemask malt die 1-Bits | Eine Schablone mit dem Default Decode [0 1] malt die 0-Bits (§8.9.6.2) | Ein einfarbiges Logo druckt als sein Negativ |
| Roman-Standard-14-Fonts fallen auf StandardEncoding zurück | Der Name StandardEncoding hat keine besondere Bedeutung (§9.6.6.1) | Akzente, Anführungszeichen und Ligaturen verändern Buchstaben |
%%BoundingBox- und %%HiResBoundingBox-Kommentare definieren die Art-Fläche | Die Platzierung ist die Matrix, die der Writer emittiert | Das Artwork rückt oder skaliert aus seiner gedachten Box |
Wohin ist der Umriss nach grestore verschwunden?
Der Umriss verschwand, weil PDF den Pfad nie gesichert hat. ISO 32000-1 §8.5.1 bringt es in einen Satz: „In PDF (anders als in PostScript) ist der aktuelle Pfad nicht Teil des Grafikzustands“, und nachdem ein Pfad gemalt wurde, gibt es keinen aktuellen Pfad, bis m oder re einen neuen beginnt. PostScript-Producer bauen auf die entgegengesetzte Regel. Der übliche Weg, eine gefüllte Form mit Umriss zu zeichnen, ist gsave fill grestore stroke: die Füllung verbraucht eine gesicherte Kopie des Pfads, grestore bringt den Pfad zurück, und der Stroke malt dieselbe Geometrie. Mappt man gsave auf q, fill auf f und grestore auf Q, kommt der Stroke mit leerem Pfad an, also ist die Form gefüllt und der Umriss weg. Ein lineto nach grestore, das den wiederhergestellten Pfad fortsetzt, scheitert auf dieselbe Weise, als fehlendes Segment
uses
SysUtils, PDFlibrary;
const
// Gefülltes Dreieck mit Umriss: die Füllung läuft innerhalb von gsave/grestore,
// und der Stroke nach grestore benutzt den wiederhergestellten Pfad wieder
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 und Top gemessen von der oberen linken Ecke
// Width und Height von 0 halten die Bounding-Box-Größe von 200 x 120
if PDF.DrawPostScript(72, 72, 0, 0, TriangleEPS) = 1 then
PDF.SaveToFile('triangle.pdf')
else
Writeln('DrawPostScript failed, code ', PDF.LastErrorCode);
// Erwartet: ein hellgraues Dreieck mit schwarzem 3-Einheiten-Umriss.
// Eine wörtliche q/f/Q/S-Übersetzung füllt das Dreieck und lässt den Umriss fallen
finally
PDF.Free;
end;
end.
PDFlibPas hält seinen eigenen PostScript-Pfad, getrennt davon, was der PDF-Content-Stream gerade hält, und schreibt den PDF-Pfad erst, wenn etwas malt oder clippt. Jeder Punkt ist unter der Transformationsmatrix fixiert, die galt, als moveto oder lineto lief, wie in PostScript, wo ein späteres scale oder concat Punkte, die schon auf dem Pfad sind, nie verschiebt. currentpoint mappt den gespeicherten Punkt durch die aktuelle Matrix zurück, also antwortet er im gegenwärtigen User Space. Ein gsave hält eine Referenz auf das Ende der Pfadkette fest plus den aktuellen Punkt, die Matrix, den Font und den Farbraum, also kopieren verschachtelte Sicherungen nie ganze Pfade
Beim Clipping trennen sich die beiden Pfadmodelle erneut, und der Import folgt auch dort PostScript: clip und eoclip emittieren W n für die PDF-Seite, lassen aber den logischen Pfad an Ort und Stelle für den Stroke, der oft folgt, während rectclip, das in PostScript einen frischen leeren Pfad beginnt, re W n emittiert und ihn leert. Die Pfadspeicherung ist bei 100.000 Knoten gedeckelt, und Replays gehen auf das gemeinsame Operationsbudget, also kann eine feindliche Datei nicht gsave und stroke loopen, um die Ausgabe aufzublähen
Bilder platzieren: ImageMatrix zeigt in die andere Richtung
Ein EPS-Bild landet gespiegelt, wenn seine ImageMatrix als Platzierung behandelt wird, denn sie beschreibt die entgegengesetzte Abbildung. In PDF bindet §8.9.4 die Bildgrenze an das Einheitsquadrat des User Space mit der oberen Sample-Zeile oben, eine implizite Bild-zu-User-Matrix von [1/w 0 0 -1/h 0 1]. Die ImageMatrix von PostScript mappt den User Space in den Bildraum. Die PDF-Platzierungsmatrix ist daher [w 0 0 -h 0 h] multipliziert mit der Inversen von ImageMatrix. Für die gewöhnliche Top-down-Matrix [w 0 0 -h 0 h] ist das Produkt die Identität. Deshalb sehen naive Konverter im flüchtigen Gebrauch richtig aus. Ein mit [w 0 0 h 0 0] geschriebener Bottom-up-Scan gibt [1 0 0 -1 0 1], eine vertikale Spiegelung im Einheitsquadrat, und rotierte oder verzerrte Matrizen brauchen keinen Sonderfall. Endet eine endliche Datenquelle früh, malt PDFlibPas nur die vollständigen Zeilen, hält die originale ImageMatrix und skaliert nach den Zeilen, die tatsächlich ankamen, also füllt ein abgeschnittenes Bild das obere Band seiner Fläche im korrekten Maßstab; die überlebenden Zeilen über die volle Höhe zu strecken würde ein verzerrtes Bild produzieren, das intakt aussieht
Welches Bit malt imagemask?
Mit true als Polarity-Operand malt imagemask die 1-Bits, und in PDF ist das Decode [1 0]. ISO 32000-1 §8.9.6.2 definiert [0 1] als Default für eine Bildmaske, das heißt, ein 0-Sample markiert die Seite mit der aktuellen Farbe, und eine 1 lässt den Hintergrund in Ruhe, und [1 0] kehrt das um. Der PostScript-Boolean sagt, welcher Samplewert malt. Als „invertiere den PDF-Default“ gelesen, druckt das Logo als Negativ, ein massiver Block mit dem Zeichen aus ihm herausgeschnitten. PDFlibPas mappt true auf [1 0] und false auf [0 1], emittiert die Schablone als Inline-Bild mit /IM true, sodass sie in der Farbe malt, die das Programm gesetzt hat, und akzeptiert in der Dictionary-Form nur diese zwei Decode-Arrays
Zwölf-Bit-Samples verstecken eine kleinere Diskrepanz. PDF hat keine 12-Bit-Bilder; §8.9.5.1 erlaubt 1, 2, 4, 8 und, ab PDF 1.5, 16 Bits pro Komponente. Jedes Sample um vier Bits nach links zu schieben passt es in 16 Bits, ohne etwas zu verlieren, aber der größte Wert wird 65520, nicht 65535, also kommt mit dem Default-Decode jedes helle Sample etwa 0,023 Prozent zu dunkel heraus. Der Import behält die Verschiebung und streckt den oberen Decode-Endpunkt um 65535/65520, was jeden normalisierten Wert wiederherstellt. Ein 12-Bit-Bild hebt die Ausgabeversion automatisch auf PDF 1.5; im PDF/A- oder PDF/X-Modus, oder wenn die Ausgabeversion unter 1.5 festgelegt ist, stoppt der Import mit Fehler 538, bevor sich die Seite ändert
Warum kommt Æ als á heraus?
Ein Roman-Font in PostScript benutzt StandardEncoding, außer das Programm kodiert ihn um, und PDF kann diese Tabelle nicht benennen: ISO 32000-1 §9.6.6.1 gibt dem Namen keine besondere Bedeutung und lässt WinAnsiEncoding, MacRomanEncoding und MacExpertEncoding als die vordefinierten Wahlen. Die Tabellen in Annex D.2 zeigen, was schiefgeht, wenn die Codes unverändert an WinAnsi übergeben werden: Oktal 341 (0xE1) ist AE in StandardEncoding und aacute in WinAnsi, also wird Æ zu á; 0xAE ist die fi-Ligatur in dem einen und ® in dem anderen; 0x27 ist quoteright in StandardEncoding und ein gerades quotesingle in WinAnsi, was typografische Apostrophe in Schreibmaschinen-Apostrophe verwandelt. Für jeden Roman-Standard-14-Font, den ein Programm benutzt, schreibt PDFlibPas eine private Type-1-Font-Ressource, deren /Encoding-Dictionary /BaseEncoding /WinAnsiEncoding und ein /Differences-Array hat, das alle 256 Codes in StandardEncoding-Reihenfolge benennt, also hängt kein Code davon ab, wie ein Viewer seine Basistabelle füllt. Die Breiten kommen aus derselben Kodierung, eine ToUnicode-Map lässt Kopieren und Suchen Æ zurückgeben, und Symbol und ZapfDingbats behalten ihre eingebauten Codes. Der Font, den Ihr Code vor dem Import gewählt hat, bleibt unberührt, denn einen Cache-Font umzukodieren, um dem EPS zu gefallen, würde Text ändern, der bereits damit platziert wurde
Diese Ressourcen benennen die Fonts, ohne sie einzubetten, was mit Profilen kollidiert, die Einbettung verlangen. In einem Dokument, das für PDF/A, PDF/X oder PDF/UA eingerichtet ist, oder nach SetEmbedAllFonts(1) scheitert EPS-Text mit Fehler 538, und die Seite bleibt, wie sie war. Für Archivausgabe ist der sauberere Weg ein EPS, exportiert mit Text, der in Outlines konvertiert wurde, das in jedem Modus als schlichte Pfade importiert; Fonts, die in einer bestehenden Datei bereits uneingebettet bleiben, brauchen stattdessen fehlende Fonts für PDF/A einzubetten. EPS-Text mappt auf die 14 Standard-Fonts über ihre exakten Namen; Artwork, das sein eigenes Fontprogramm einbettet, importiert sauber, sobald sein Text zu Outlines geworden ist, was auch einen lokalen Substitutions-Font davon abhält, jede Zeile zu verschieben
Bounding Boxes, DSC-Kommentare und binäre EPS-Dateien
Die Platzierung skaliert die Bounding Box des Artworks in das Rechteck, das Sie übergeben, also muss die Box vom richtigen Ort kommen. PDFlibPas liest %%BoundingBox und %%HiResBoundingBox nur aus echten DSC-Kommentarzeilen, das heißt, Kommentare auf Top-Level, die eine Zeile beginnen, und eine gültige hochauflösende Box gewinnt über die Integer-Box. Eine schlichte Teilstring-Suche findet diese Schlüsselwörter auch innerhalb von Literal-Strings, Prozedurkörpern und Binär-Bilddaten. EPS-Dateien tragen alle drei. Ein Header-Wert von (atend) schiebt die Zahlen auf einen Kommentar im Trailer, gelesen, wenn der Scanner ihn erreicht; eine Datei ohne jede Box fällt auf 612 mal 792 zurück, und eine leere oder invertierte Box scheitert mit Fehler 124. Width und Height skalieren die Box unabhängig: 0 für beide übergeben, um die natürliche Größe zu halten, oder beide im Seitenverhältnis der Box übergeben, denn ein einzelner Wert ungleich null streckt das Artwork nur entlang einer Achse
Binärdaten entscheiden den Einstiegspunkt. DrawPostScript nimmt einen WideString, fein für PostScript, das im Code gebaut wurde, aber rohe Bildsamples, durch currentfile gelesene Flate-Daten und der DOS-EPS-Wrapper enthalten Bytes, die eine Reise durch Unicode-Text nicht überleben. ImportEPSFromFile liest die Datei als rohe Bytes, erkennt den C5 D0 D3 C6-Preview-Header, prüft, dass Offset und Länge des PostScript-Segments in die Datei fallen, und führt nur dieses Segment aus, während es die TIFF- oder WMF-Vorschau ignoriert. Beide Aufrufe nehmen höchstens 4 MiB Quelle, verlangen Options = 0, geben 1 oder 0 zurück und hängen entweder die ganze Zeichnung oder nichts an. Der Fehlergrund sitzt in LastErrorCode, das sich beim Lesen zurücksetzt, also holen Sie ihn einmal ab
uses
SysUtils, PDFlibrary;
function PlaceEPS(PDF: TPDFlib; const FileName: string;
Left, Top: Double): Boolean;
var
Code: Integer;
begin
// 0, 0: natürliche Größe aus %%HiResBoundingBox oder %%BoundingBox
Result := PDF.ImportEPSFromFile(Left, Top, 0, 0, FileName) = 1;
if Result then
Exit;
Code := PDF.LastErrorCode; // Lesen leert den Code, also einmal lesen
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.
Wenn Fehler 538 einen Operator benennt, exportieren Sie das Artwork aus seinem Editor neu als schlichtes EPS mit Text, der zu Outlines konvertiert wurde: das geht meist schneller, als dem Operator hinterherzujagen, und Outline-Artwork platziert auch in einem PDF/A- oder PDF/UA-Dokument, gebaut mit der PDFlibPas Delphi PDF Library, ohne den Standard-14-Font-Konflikt