Logo EPS położone na stronie PDF może wyjść z brakującym obrysem, jednokolorowy znak może zostać wydrukowany jako własny negatyw, a Æ może zamienić się w á. Każdy objaw wskazuje miejsce, gdzie semantyka grafiki PostScript i PDF się rozchodzi, więc import EPS w PDFlibPas dla Delphi interpretuje program PostScript i wypisuje każdą różnicę jako natywne operatory PDF
Interpretacja programu albo przepuszczenie wprost
PostScript może siedzieć w PDF na dwa sposoby, i tylko jeden z nich pokazuje cokolwiek na ekranie. ISO 32000-1 §8.8.2 definiuje PostScript XObject, strumień /Subtype /PS, którego bajty są kopiowane, bez interpretacji, do zadania druku tylko wtedy, gdy urządzeniem wyjściowym jest drukarka PostScript; wszędzie indziej nie działa, a przegląd §8.8 mówi, że PostScript XObjectów nie należy używać. PDF/A wyklucza je wprost. TPDFlib.NewPostScriptXObject i DrawPostScriptXObject zapisują formę przepuszczającą wprost, podczas gdy ImportEPSFromFile i DrawPostScript puszczają program przez interpreter i emitują zwykłe ścieżki, obrazy inline i tekst. Grafikę wysyłaj przez interpreter, a PS XObject zostaw dla fragmentu celowanego w RIP jednej drukarni, rastrowy procesor obrazu przed maszyną albo drukarką produkcyjną. Interpretacja EPS to też inna robota niż import wektorów EMF, gdzie wejściem jest lista rekordów GDI do odpalenia; plik EPS to program
procedure AddPrintRoomHook(PDF: TPDFlib);
var
PSRef: Integer;
begin
// Składowany verbatim jako strumień /Subtype /PS i referencjonowany przez Do.
// Wykonuje się tylko na drukarce PostScript; przeglądarki nie rysują nic
PSRef := PDF.NewPostScriptXObject(
'userdict /PrintRoomMark known { PrintRoomMark } if');
if PSRef <> 0 then
PDF.DrawPostScriptXObject(PSRef);
end;
| Zachowanie PostScript | Zachowanie PDF | Wynik tłumaczenia literalnego |
|---|---|---|
gsave / grestore zapisują i przywracają bieżącą ścieżkę i bieżący punkt | q / Q zostawiają ścieżkę w spokoju, a malowanie ją kończy (§8.5.1) | Obrys albo kontynuowany segment po grestore znika |
clip zachowuje bieżącą ścieżkę do późniejszego malowania | Po W idzie operator malujący, zwykle n, który kończy ścieżkę | Obrys po clipie nie ma nic do narysowania |
ImageMatrix mapuje przestrzeń użytkownika do przestrzeni obrazu | Każdy obraz wypełnia kwadrat jednostkowy, pierwszy wiersz u góry (§8.9.4) | Obrazy od dołu w górę albo obrócone lądują odwrócone albo przesunięte |
true imagemask maluje bity 1 | Stencil z domyślnym Decode [0 1] maluje bity 0 (§8.9.6.2) | Jednokolorowe logo drukuje się jako swój negatyw |
| Fonty rzymskie Standard-14 domyślnie używają StandardEncoding | Nazwa StandardEncoding nie ma szczególnego znaczenia (§9.6.6.1) | Akcenty, cudzysłowy i ligatury zmieniają litery |
Komentarze %%BoundingBox i %%HiResBoundingBox definiują obszar grafiki | Rozmieszczenie to jakakolwiek macierz, którą emituje writer | Grafika przesuwa się albo skaluje poza zamierzonym polem |
Gdzie się podział obrys po grestore?
Obrys zniknął, bo PDF nigdy nie zapisuje ścieżki. ISO 32000-1 §8.5.1 mieści to w jednym zdaniu: „W PDF (w odróżnieniu od PostScript) bieżąca ścieżka nie jest częścią stanu grafiki”, a po namalowaniu ścieżki nie ma bieżącej ścieżki, dopóki m albo re nie zaczną nowej. Producenci PostScript polegają na przeciwnej regule. Zwykły sposób narysowania wypełnionego kształtu z obrysem to gsave fill grestore stroke: wypełnienie zużywa zapisaną kopię ścieżki, grestore przyprowadza ścieżkę z powrotem, a obrys maluje tę samą geometrię. Zmapuj gsave na q, fill na f i grestore na Q, i obrys przychodzi z pustą ścieżką, więc kształt jest wypełniony, a obrys zniknął. lineto po grestore, kontynuujące przywróconą ścieżkę, zawodzi tak samo — jako brakujący segment
uses
SysUtils, PDFlibrary;
const
// Wypełniony trójkąt z obrysem: wypełnienie biegnie wewnątrz gsave/grestore,
// a obrys po grestore reużywa przywróconej ścieżki
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 i Top mierzone od lewego górnego rogu
// Width i Height 0 zachowują rozmiar bounding box 200 x 120
if PDF.DrawPostScript(72, 72, 0, 0, TriangleEPS) = 1 then
PDF.SaveToFile('triangle.pdf')
else
Writeln('DrawPostScript failed, code ', PDF.LastErrorCode);
// Oczekiwane: jasnoszary trójkąt z czarnym obrysem 3 jednostki.
// Literalne tłumaczenie q/f/Q/S wypełnia trójkąt i gubi obrys
finally
PDF.Free;
end;
end.
PDFlibPas trzyma własną ścieżkę PostScript, osobno od tego, co strumień treści PDF akurat trzyma, i zapisuje ścieżkę PDF tylko wtedy, gdy coś maluje albo clipuje. Każdy punkt jest przybity pod macierzą transformacji obowiązującą w chwili, gdy zadziałało moveto albo lineto, jak w PostScript, gdzie późniejszy scale albo concat nigdy nie rusza punktów już będących na ścieżce. currentpoint mapuje zapisany punkt z powrotem przez bieżącą macierz, więc odpowiada w obecnej przestrzeni użytkownika. gsave odnotowuje referencję do końca łańcucha ścieżek plus bieżący punkt, macierz, font i przestrzeń kolorów, więc zagnieżdżone zapisy nigdy nie kopiują całych ścieżek
Clipping to miejsce, gdzie oba modele ścieżek znów się rozchodzą, i import idzie tu za PostScript: clip i eoclip emitują W n po stronie PDF, ale zostawiają logiczną ścieżkę na miejscu dla obrysu, który często następuje, podczas gdy rectclip, zaczynający w PostScript świeżą pustą ścieżkę, emituje re W n i ją czyści. Magazyn ścieżek ma sufit 100 000 węzłów, a replaye czerpią ze wspólnego budżetu operacji, więc wrogi plik nie zapętli gsave i stroke, żeby nadmuchać wyjście
Rozmieszczanie obrazów: ImageMatrix wskazuje w drugą stronę
Obraz EPS ląduje odwrócony, gdy jego ImageMatrix jest traktowany jako rozmieszczenie, bo opisuje mapowanie odwrotne. W PDF §8.9.4 wiąże granicę obrazu z kwadratem jednostkowym przestrzeni użytkownika, z górnym wierszem próbek u góry — niejawną macierzą obraz-do-użytkownika [1/w 0 0 -1/h 0 1]. PostScriptowe ImageMatrix mapuje przestrzeń użytkownika do przestrzeni obrazu. Macierz rozmieszczenia PDF to więc [w 0 0 -h 0 h] przemnożona przez odwrotność ImageMatrix. Dla częstej macierzy od góry w dół [w 0 0 -h 0 h] iloczyn to identyczność. Dlatego naiwne konwertery wyglądają dobrze w niedbałym użyciu. Skan od dołu w górę zapisany [w 0 0 h 0 0] daje [1 0 0 -1 0 1], pionowe odbicie wewnątrz kwadratu jednostkowego, a macierze obrócone albo ścięte nie potrzebują osobnego przypadku. Gdy skończone źródło danych urywa się wcześnie, PDFlibPas maluje tylko kompletne wiersze, zachowuje pierwotne ImageMatrix i skaluje według wierszy, które faktycznie dotarły, więc ścięty obraz wypełnia górny pas swojego obszaru we właściwej skali; rozciągnięcie ocalałych wierszy na pełną wysokość dałoby zniekształcony obraz wyglądający na nienaruszony
Który bit maluje imagemask?
Z true jako operandem polarności imagemask maluje bity 1, a w PDF to Decode [1 0]. ISO 32000-1 §8.9.6.2 definiuje [0 1] jako domyślne dla maski obrazu, co znaczy, że próbka 0 znaczuje stronę bieżącym kolorem, a 1 zostawia tło w spokoju, i [1 0] to odwraca. PostScriptowy boolean mówi, która wartość próbki maluje. Przeczytaj go jako „odwróć domyślne PDF”, i logo wydrukuje się jako negatyw, pełny blok z wyciętym w nim znakiem. PDFlibPas mapuje true na [1 0] i false na [0 1], emituje stencil jako obraz inline z /IM true, więc maluje w kolorze, który ustawił program, i w formie słownikowej przyjmuje tylko te dwie tablice Decode
Dwunastobitowe próbki kryją mniejszą niezgodność. PDF nie ma obrazów 12-bitowych; §8.9.5.1 pozwala 1, 2, 4, 8 i, od PDF 1.5, 16 bitów na składową. Przesunięcie każdej próbki w lewo o cztery bity mieści ją w 16 bitach bez utraty czegokolwiek, ale największa wartość staje się 65520, nie 65535, więc przy domyślnym Decode każda jasna próbka wychodzi o około 0,023 procenta za ciemna. Import zachowuje przesunięcie i rozciąga górny punkt Decode przez 65535/65520, co przywraca każdą znormalizowaną wartość. Obraz 12-bitowy podciąga wersję wyjściową do PDF 1.5 automatycznie; w trybie PDF/A albo PDF/X, albo gdy wersja wyjściowa jest zablokowana poniżej 1.5, import stopuje z błędem 538, zanim strona się zmieni
Czemu Æ wychodzi jako á?
Font rzymski w PostScript używa StandardEncoding, chyba że program zakoduje go ponownie, a PDF nie potrafi nazwać tej tablicy: ISO 32000-1 §9.6.6.1 nie nadaje nazwie szczególnego znaczenia, zostawiając WinAnsiEncoding, MacRomanEncoding i MacExpertEncoding jako wybory predefiniowane. Tablice z załącznika D.2 pokazują, co idzie nie tak, gdy kody trafią do WinAnsi bez zmian: ósemkowo 341 (0xE1) to AE w StandardEncoding i aacute w WinAnsi, więc Æ staje się á; 0xAE to ligatura fi w jednym i ® w drugim; 0x27 to quoteright w StandardEncoding i prosty quotesingle w WinAnsi, co zamienia typograficzne apostrofy w maszynowe. Dla każdego fontu rzymskiego Standard-14 użytego przez program PDFlibPas zapisuje prywatny zasób fontu Type 1, którego słownik /Encoding ma /BaseEncoding /WinAnsiEncoding i tablicę /Differences nazywającą wszystkie 256 kodów w kolejności StandardEncoding, więc żaden kod nie zależy od tego, jak przeglądarka wypełni swoją tabelę bazową. Szerokości pochodzą z tego samego kodowania, mapa ToUnicode pozwala kopiowaniu i szukaniu zwracać Æ, a Symbol i ZapfDingbats zachowują swoje wbudowane kody. Font wybrany w kodzie przed importem zostaje nietknięty, bo przekodowanie fontu trzymanego w cache pod kątem EPS zmieniłoby tekst już nim ułożony
Te zasoby nazywają fonty bez osadzania ich, co koliduje z profilami wymagającymi osadzania. W dokumencie ustawionym na PDF/A, PDF/X albo PDF/UA, albo po SetEmbedAllFonts(1), tekst EPS zawodzi z błędem 538, a strona zostaje taka, jak była. Dla wyjścia archiwalnego czystsza droga to EPS wyeksportowany z tekstem zamienionym na krzywe, który importuje się jako zwykłe ścieżki w każdym trybie; fonty już zostawione nieosadzone w istniejącym pliku potrzebują zamiast tego osadzania brakujących fontów dla PDF/A. Tekst EPS mapuje się na 14 standardowych fontów po ich dokładnych nazwach; grafika osadzająca własny program fontowy importuje się czysto, skoro jej tekst jest na krzywych, co też chroni przed ruszeniem każdej linii przez lokalny font zastępczy
Bounding boxy, komentarze DSC i binarne pliki EPS
Rozmieszczenie skaluje bounding box grafiki do prostokąta, który podasz, więc pole musi przyjść z właściwego miejsca. PDFlibPas czyta %%BoundingBox i %%HiResBoundingBox wyłącznie z prawdziwych linii komentarzy DSC, czyli komentarzy najwyższego poziomu zaczynających linię, i poprawne pole o wysokiej rozdzielczości wygrywa z całkowitym. Gołe szukanie podłańcucha znajduje te słowa kluczowe także wewnątrz literalnych łańcuchów, ciał procedur i binarnych danych obrazu. Pliki EPS niosą wszystkie trzy. Wartość nagłówka (atend) odkłada liczby do komentarza w zwiastunie, czytanego, gdy skaner tam dotrze; plik bez żadnego pola wraca do 612 na 792, a puste albo odwrócone pole zawodzi z błędem 124. Width i Height skalują pole niezależnie: podaj 0 dla obu, żeby zachować naturalny rozmiar, albo podaj oba w proporcji pola, bo pojedyncza niezerowa wartość rozciąga grafikę wzdłuż jednej osi
Dane binarne rozstrzygają punkt wejścia. DrawPostScript bierze WideString — dobrze dla PostScript zbudowanego w kodzie, ale surowe próbki obrazu, dane Flate czytane przez currentfile i wrapper DOS EPS zawierają bajty, które nie przeżyją podróży przez tekst Unicode. ImportEPSFromFile czyta plik jako surowe bajty, rozpoznaje nagłówek podglądu C5 D0 D3 C6, sprawdza, że offset i długość segmentu PostScript mieszczą się w pliku, i wykonuje wyłącznie ten segment, ignorując podgląd TIFF albo WMF. Oba wywołania biorą najwyżej 4 MiB źródła, wymagają Options = 0, zwracają 1 albo 0 i doklejają albo cały rysunek, albo nic. Powód porażki siedzi w LastErrorCode, który resetuje się przy czytaniu, więc złap go raz
uses
SysUtils, PDFlibrary;
function PlaceEPS(PDF: TPDFlib; const FileName: string;
Left, Top: Double): Boolean;
var
Code: Integer;
begin
// 0, 0: naturalny rozmiar z %%HiResBoundingBox albo %%BoundingBox
Result := PDF.ImportEPSFromFile(Left, Top, 0, 0, FileName) = 1;
if Result then
Exit;
Code := PDF.LastErrorCode; // czytanie czyści kod, więc przeczytaj go raz
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.
Gdy błąd 538 nazywa operator, wyeksportuj grafikę z edytora ponownie jako zwykły EPS z tekstem zamienionym na krzywe: to zwykle szybsze niż gonienie operatora, a grafika na krzywych rozmieszcza się też w dokumencie PDF/A albo PDF/UA zbudowanym z PDFlibPas Delphi PDF Library bez konfliktu fontów Standard-14