PDFlibPas produkuje pliki PDF/X do druku z Delphi jednym wywołaniem, TPDFlib.SetPDFXMode, które wybiera PDF/X-1a:2003, PDF/X-3:2003 albo PDF/X-4. To wywołanie zapisuje identyfikację XMP pdfxid, dodaje output intent GTS_PDFX, deklaruje /Trapped /False, przypina wersję PDF i od tej chwili odmawia wywołań rysujących zabronionych przez wybraną część. CheckFileCompliance z testem 3 audytuje zapisany plik
To krótka odpowiedź. Dłuższa ma znaczenie, bo drukarnie nie odrzucają plików za brzydotę, odrzucają je na preflighcie, zwykle w piątek, zwykle o czymś, co kod generujący mógł wyłapać sam: RGB-owe logo w zleceniu X-1a, font, do którego się odwołano zamiast go osadzić, strona bez formatu cięcia. PDF/X istnieje po to, by takie porażki były strukturalne, a nie wizualne, a rola biblioteki polega na tym, żebyś w ogóle ich nie wyprodukował
Na jaką część PDF/X powinna celować aplikacja Delphi?
Wybieraj PDF/X-4, dopóki odbierający workflow nie powie inaczej, i wybieraj PDF/X-1a, gdy powie. Trzy części różnią się dokładnie wzdłuż trzech osi: koloru, przezroczystości i bazowej wersji PDF, a każda oś mapuje się na bramkę wewnątrz PDFlibPas:
- PDF/X-1a:2003 (ISO 15930-4): składnia PDF 1.4, wyłącznie CMYK, szarości i kolory spot, bez koloru opartego na ICC, bez przezroczystości. Plik jest już rozseparowany na kolory maszynowe, więc drukarnia nie robi żadnej konwersji koloru
- PDF/X-3:2003 (ISO 15930-6): nadal PDF 1.4 i nadal bez przezroczystości, ale przestrzenie zarządzane kolorem są dozwolone, a device RGB jest akceptowany, gdy sam output intent opisuje warunek RGB
- PDF/X-4 (ISO 15930-7): składnia PDF 1.6, z żywą przezroczystością i treścią opcjonalną (warstwami). To część, dla której buduje się nowoczesne RIP-y, bo spłaszczanie przezroczystości na wyjściu było źródłem sporej części dawnego bólu X-1a
X-1a jest wciąż wszędzie w dostarczaniu reklam i w starszych workflow drukarskich, które chcą plik do naświetlenia bez myślenia. X-4 to lepszy inżynierski wybór, gdziekolwiek drukarnia go przyjmuje: przezroczystość przetrwa nietknięta, a decyzje kolorystyczne zapadają na RIP-ie z prawdziwym profilem maszynowym, a nie w twoim kodzie. Numery trybów to odpowiednio 1, 2 i 3, a GetInformation(203) raportuje z powrotem aktywny tryb. PDFlibPas definiuje też wyższe numery trybów dla rodziny PDF/X-6 na PDF 2.0, które podlegają innym regułom wersji i output intentu i są poza zakresem tego artykułu
Co SetPDFXMode zapisuje do pliku
TPDFlib.SetPDFXMode ogarnia identyfikację i boilerplate, które czytnik PDF/X sprawdza w pierwszej kolejności, więc kod aplikacji musi dostarczyć tylko prawdziwy profil maszynowy i treść. Konkretnie, udane wywołanie robi cztery rzeczy:
- Zapisuje string edycji do XMP jako
pdfxid:GTS_PDFXVersion(PDF/X-1a:2003,PDF/X-3:2003alboPDF/X-4). Edycje 2003 identyfikują część całkowicie przez ten string, więc osobna właściwość zgodności nie jest zapisywana - Dodaje output intent, jeśli dokument go nie ma, i ustawia jego podtyp na
/S /GTS_PDFX, podtyp, którego szukają czytniki PDF/X (ISO 32000-1 §14.11.5 definiuje słownik output intentu) - Ustawia
/Trapped /Falsew słowniku informacji o dokumencie, bo writer nigdy nie trapuje, a PDF/X wymaga, by stan trapowania był zadeklarowany, a nie pozostawiony nieznany - Przypina bazę wersji: 1.4 dla X-1a i X-3, 1.6 dla X-4
Dwie kolejne naprawy dzieją się w momencie zapisu. Każda strona bez TrimBox dostaje jeden równy swojemu MediaBox, bo PDF/X wymaga zadeklarowanego formatu wykończenia na każdej stronie (ISO 32000-1 §14.11.2 pokrywa granice strony), a adnotacje dostają flagę druku oczekiwaną przez standard. Fallback TrimBox to siatka bezpieczeństwa, nie decyzja layoutowa. Zlecenie full-bleed potrzebuje prawdziwego pola cięcia wewnątrz większego pola mediów, a przewodnik po domyślnych TrimBox, BleedBox i CropBox w PDFlibPas omawia, jak ustawić te pola i jak dziedziczenie potrafi zaskoczyć
uses
PDFlibrary;
procedure BuildX1aAd(const OutFile, PressProfile: string);
var
Pdf: TPDFlib;
begin
Pdf := TPDFlib.Create;
try
if Pdf.SetPDFXMode(1) <> 1 then // 1 = PDF/X-1a:2003
raise Exception.CreateFmt('PDF/X mode refused (error %d)',
[Pdf.LastErrorCode]);
// Podmień domyślny profil na prawdziwy warunek maszynowy
if Pdf.LoadOutputIntentProfile(PressProfile, 'DeviceCMYK') <> 1 then
raise Exception.Create('Could not load the CMYK output profile');
// Embed = 1 osadza pełny program fontu; PDF/X nie przyjmie 0
if Pdf.AddTrueTypeFont('Arial {1252}', 1) = 0 then
raise Exception.Create('Font could not be loaded for embedding');
Pdf.SetTextSize(24);
Pdf.SetFillColorCMYK(0, 1, 1, 0); // procesowy czerwony, bez RGB
Pdf.DrawText(72, 720, 'Spring catalog, page 1');
Pdf.SaveToFile(OutFile);
finally
Pdf.Free;
end;
end;
Kolejność w tym szkicu jest świadoma. LoadOutputIntentProfile podmienia profil output intentu, który już istnieje, więc musi się uruchomić po tym, jak SetPDFXMode go utworzy. To wywołanie nie potrafi też czarować intentu z niczego: na dokumencie bez output intentu po prostu zwraca 0
Dlaczego domyślny output intent trzeba podmienić?
Output intent, który SetPDFXMode dodaje dla trybów 1 do 3, deklaruje DeviceCMYK, ale bajty ICC za nim to dołączony do biblioteki profil sRGB z przepisaną na 4 liczbą komponentów. Ten domyślny istnieje dla wstecznej zgodności i wystarcza, żeby plik był strukturalnie PDF/X, tylko że osadzony profil nie opisuje żadnej drukarni. Narzędzie preflight, które zagląda w nagłówek profilu, zobaczy profil RGB w mundurku z etykietą CMYK, a drukarnia polegająca na incencie przy proofach dostanie bzdury
Traktuj więc LoadOutputIntentProfile(FileName, 'DeviceCMYK') jako obowiązkowe przy prawdziwej pracy drukarskiej i podaj profil ICC, który twój dostawca druku wskazuje dla danego papieru i maszyny. TPDFlib.LoadOutputIntentProfile podmienia strumień DestOutputProfile i aktualizuje /N, a resztę słownika intentu zostawia jak była. W szczególności identyfikator warunku wyjściowego i stringi opisowe domyślnego intentu wciąż niosą wartości w stylu sRGB, więc jeśli twoja drukarnia matchuje po identyfikatorze, a nie po osadzonym profilu, ustal z nią przed wysyłką, co dokładnie sprawdza. To prawdziwa luka domyślnych z ery 2003, nie coś, co twój kod zrobił źle
Pułapka pierwsza: wersja, której nie wybrałeś
Nowy dokument PDFlibPas startuje od writerowego domyślnego PDF 1.5, który jest ponad bazą 1.4 wymaganą przez PDF/X-1a i PDF/X-3. TPDFlib.SetPDFXMode załatwia to po cichu w zwykłym przypadku: jeśli wersja zapisu nie została zablokowana, obniża wersję dokumentu do bazy części i jedzie dalej. Pierwsza implementacja zamiast tego odmawiała i każde SetPDFXMode(1) na świeżym dokumencie zwracało 0, co jest dobrym przykładem reguły poprawnej na papierze i bezużytecznej w praktyce
Odmowa nadal istnieje dla tego jednego przypadku, w którym jest słuszna. Jeśli coś wcześniej wywołało LockSaveVersion z wersją ponad bazę, SetPDFXMode zwraca 0 z LastErrorCode 601, bo wywołujący przypiął wersję celowo, a biblioteka nie będzie tego nadpisywać. We wspólnym kodzie konfiguracji dokumentu, gdzie helper blokuje wersję z powodów niezwiązanych z PDF/X, dokładnie tu umiera zlecenie X-1a:
Pdf := TPDFlib.Create;
try
Pdf.LockSaveVersion('1.7'); // ustawione przez generyczny helper "domowych domyślnych"
if Pdf.SetPDFXMode(1) = 0 then
// LastErrorCode = 601: 1.7 zablokowane, PDF/X-1a potrzebuje 1.4.
// Pomijaj blokadę przy zleceniach drukarskich. PDF/X-4 to tu nie ratunek:
// jego baza 1.6 też jest pod blokadą 1.7 i pada tak samo.
LogError('PDF/X refused, error %d', [Pdf.LastErrorCode]);
finally
Pdf.Free;
end;
Ta sama bramka tłumaczy pokrewne ograniczenie. Tryb PDF/X to przełącznik tworzenia dokumentu: SetPDFXMode działa na nowym dokumencie i zwraca 0 na takim wczytanym z pliku, bo biblioteka nie może ręczyć za treść, której nie napisała. Konwersja istniejącego PDF-a do PDF/X to inna robota, wymagająca inspekcji i naprawy, nie flagi
Pułapka druga: bramki, które odmawiają po cichu
Gdy tryb PDF/X jest aktywny, PDFlibPas odmawia każdego wywołania treści zabronionego przez wybraną część, w momencie, gdy je robisz, zamiast pozwolić plikowi polec na preflighcie później. Odmowy obejmują wypełnienia i kontury w device RGB (całe X-1a; X-3 i X-4, chyba że output intent jest RGB), kolory oparte na ICC w X-1a, przezroczystość i tryby mieszania w X-1a i X-3, treść opcjonalną w X-1a i X-3, akcje JavaScript, załączniki plikowe, filtr obrazów LZW, fonty Standard 14, które poszłyby nieosadzone, oraz szyfrowanie z kluczem publicznym. Szyfrowanie hasłem przez Encrypt nie jest bramkowane w czasie tworzenia, ale walidator flaguje każdy zaszyfrowany plik, więc trzymaj zlecenia drukarskie niezaszyfrowane
Czego odmowy nie współdzielą, to kod błędu, i to jest pułapka. Bramki przestrzeni barw w SetFillColor, SetLineColor, wywołaniach koloru opartego na ICC oraz EncryptPublicKey ustawiają LastErrorCode na 601, kod zgodności biblioteki, którego używają też bramki PDF/A. Bramki przezroczystości, JavaScriptu, załączników, LZW i AddStandardFont po prostu zwracają 0 i zostawiają LastErrorCode nietknięte, co znaczy, że może on wciąż trzymać wartość z wcześniejszego wywołania. Kod, który po paczce wywołań rysujących sprawdza tylko LastErrorCode, przegapi połowę odmów, więc sprawdzaj każdą wartość zwracaną:
procedure Require(Ok: Integer; const What: string);
begin
if Ok = 0 then
raise EPrintConformance.CreateFmt('%s refused under PDF/X', [What]);
end;
Require(Pdf.SetFillColorCMYK(0, 0, 0, 1), 'CMYK fill');
Require(Pdf.SetTransparency(40), 'transparency'); // 0 w X-1a / X-3
Require(Pdf.AddLinkToJavaScript(72, 72, 120, 20,
'app.alert(1)', 0), 'JavaScript link'); // 0 w każdej części
Fonty zasługują na własne ostrzeżenie. AddStandardFont zwraca 0 pod PDF/X, bo biblioteka nie dostarcza konturów Standard 14 i nie może ich osadzić. AddTrueTypeFont zachowuje się inaczej: w trybie PDF/A podbija Embed = 0 do pełnego osadzenia za ciebie, ale w trybie PDF/X już nie, więc systemowy font, do którego się tylko odwołano, trafia do pliku i wyłapuje go dopiero walidator. Przekazuj sam 1 (pełne) albo 2 (podzbiór) albo wywołaj raz przy konfiguracji SetEmbedAllFonts(1), żeby zbłąkane 0 nie przecisnęło się przez bramkę
Jak zweryfikować plik PDF/X, zanim pójdzie do drukarni?
Puść CheckFileCompliance z ComplianceTest = 3 na zapisanym pliku. TPDFlib.CheckFileCompliance zwraca 0, gdy nie zanotował ustaleń, a w przeciwnym razie uchwyt listy stringów, której pozycje to linie z prefiksem PDFX-. Audyt PDF/X czyta plik tym samym streamingowym czytnikiem co preflight PDF/A i PDF/UA w PDFlibPas i sprawdza reguły, które drukarski preflight sprawdza w pierwszej kolejności:
- Plik nie jest zaszyfrowany, XMP niesie znany
GTS_PDFXVersion, a wersja PDF nie przekracza bazy części - Istnieje dokładnie jeden output intent, jego podtyp to
GTS_PDFX, a on niesie profil docelowy i identyfikator warunku wyjściowego - Słownik informacji deklaruje
/Trapped - Każda strona ma TrimBox mieszczący się wewnątrz MediaBox, a BleedBox, jeśli jest, obejmuje TrimBox
- Każdy font użyty na stronie ma osadzony program, przy czym fonty Type0 są sprawdzane przez ich font potomny
- Żadnych zabronionych typów adnotacji (RichMedia, Screen, 3D, Movie, Sound, FileAttachment), żadnej akcji otwarcia JavaScript ani JavaScriptu poziomu dokumentu, żadnych osadzonych plików i żadnego filtra LZWDecode gdziekolwiek w grafie obiektów
function PreflightPDFX(Pdf: TPDFlib; const FileName: string;
Report: TStrings): Boolean;
var
ListId, I: Integer;
begin
ListId := Pdf.CheckFileCompliance(FileName, '', 3, 0); // 3 = PDF/X
Result := ListId = 0;
if Result then
Exit;
try
for I := 1 to Pdf.GetStringListCount(ListId) do // indeksy liczone od 1
Report.Add(Pdf.GetStringListItem(ListId, I));
finally
Pdf.ReleaseStringList(ListId);
end;
end;
Miej jasność, co znaczy ten przebieg. Walidator PDFlibPas audytuje strukturalne reguły wypisane wyżej i to one wyłapują błędy generatora. To nie jest preflight wierny kolorystycznie: nie sprawdza sumarycznego nakładu farby, rozdzielczości obrazów ani tego, czy osadzony profil pasuje do warunku, na którym drukuje twoja drukarnia. Używaj go jako bramki w buildzie albo potoku eksportu, a sprawdzenia specyficzne dla maszyny zostaw profilowi preflight drukarni, bo ten i tak puści własny
FAQ
Czy PDFlibPas wspiera przezroczystość PDF/X-4?
Tak. Przy SetPDFXMode(3) PDFlibPas pozwala na przezroczystość, tryby mieszania i grupy treści opcjonalnej oraz zapisuje plik PDF 1.6. Te same wywołania zwracają 0 pod PDF/X-1a i PDF/X-3
Czy PDFlibPas skonwertuje istniejący PDF do PDF/X?
Nie. SetPDFXMode dotyczy tylko dokumentów utworzonych w bieżącej sesji TPDFlib i zwraca 0 dla pliku wczytanego. Każdy istniejący PDF możesz nadal audytować przez CheckFileCompliance z testem 3
Jaki kod błędu zwraca odmówione wywołanie PDF/X?
Odmowy przestrzeni barw i szyfrowania z kluczem publicznym ustawiają LastErrorCode na 601. Pozostałe bramki, jak przezroczystość, JavaScript i fonty Standard 14, tylko zwracają 0, więc zawsze testuj wynik funkcji
Wyjście PDF/X, walidator PDF/X i reszta zestawu narzędzi drukarskich i archiwizacyjnych są częścią PDFlibPas, natywnej biblioteki PDF w Object Pascal od losLab dla Delphi. Zobacz stronę produktu PDFlibPas Delphi PDF Library, gdzie są edycje, platformy i pełne odniesienie API