PDFlibPas выпускает печатные файлы PDF/X из Delphi одним вызовом — TPDFlib.SetPDFXMode, который выбирает PDF/X-1a:2003, PDF/X-3:2003 или PDF/X-4. Вызов записывает XMP-идентификацию pdfxid, добавляет output intent GTS_PDFX, объявляет /Trapped /False, фиксирует версию PDF и с этого момента отказывает рисующим вызовам, запрещённым выбранной частью. Аудит сохранённого файла делает CheckFileCompliance с тестом 3
Это короткий ответ. Длинный важен, потому что типографии бракуют файлы не за уродство — они бракуют их на preflight, обычно в пятницу и обычно из-за того, что мог поймать генерирующий код: RGB-логотип в задании X-1a, шрифт по ссылке вместо встроенного, страница без обрезного размера. PDF/X существует, чтобы сделать такие провалы структурными, а не визуальными, и библиотечная часть работы — не дать вам их породить вообще
Какую часть PDF/X выбрать приложению на Delphi?
Берите PDF/X-4, если принимающий workflow не говорит иного, и берите PDF/X-1a, когда говорит. Три части различаются ровно по трём осям — цвет, прозрачность и базовая версия PDF, — и каждая ось ложится на свой гейт внутри PDFlibPas:
- PDF/X-1a:2003 (ISO 15930-4): синтаксис PDF 1.4, только CMYK, серые и плашечные цвета, без цветов на базе ICC, без прозрачности. Файл уже разведён по печатным краскам, и типографии не нужно делать никакого цветоделения
- PDF/X-3:2003 (ISO 15930-6): всё ещё PDF 1.4 и по-прежнему без прозрачности, но color-managed пространства разрешены, а device RGB принимается, когда сам output intent описывает RGB-условие
- PDF/X-4 (ISO 15930-7): синтаксис PDF 1.6 с живой прозрачностью и optional content (слоями). Именно под эту часть сделаны современные RIP: немалая доля старой боли X-1a рождалась как раз из уплощения прозрачности на выходе
X-1a до сих пор повсюду в сдаче рекламы и в старых печатных workflow, которым нужен файл, выводимый на печать без раздумий. X-4 — лучшее инженерное решение везде, где типография его принимает: прозрачность доживает до RIP нетронутой, а цветовые решения принимаются на RIP с реальным профилем печатной машины, а не в вашем коде. Номера режимов — 1, 2 и 3 соответственно, активный режим возвращает GetInformation(203). PDFlibPas определяет и более высокие номера режимов для семейства PDF/X-6 на PDF 2.0, но там другие правила версий и output intent, и здесь они вне области разговора
Что SetPDFXMode записывает в файл
TPDFlib.SetPDFXMode берёт на себя идентификацию и шаблонный каркас, которые читатель PDF/X проверяет в первую очередь, так что коду приложения остаётся дать настоящий печатный профиль и контент. Конкретно успешный вызов делает четыре вещи:
- Записывает строку издания в XMP как
pdfxid:GTS_PDFXVersion(PDF/X-1a:2003,PDF/X-3:2003илиPDF/X-4). Издания 2003 года идентифицируют часть полностью этой строкой, поэтому отдельное свойство соответствия не пишется - Добавляет output intent, если его у документа нет, и выставляет его подтип в
/S /GTS_PDFX— тот подтип, который ищут читатели PDF/X (словарь output intent определён в ISO 32000-1 §14.11.5) - Выставляет
/Trapped /Falseв информационном словаре документа: писатель никогда не делает треппинг, а PDF/X требует, чтобы состояние треппинга было объявлено, а не оставлено неизвестным - Фиксирует базу версии: 1.4 для X-1a и X-3, 1.6 для X-4
Ещё два исправления происходят при сохранении. Каждая страница без TrimBox получает его равным своему MediaBox, поскольку PDF/X требует объявленного обрезного размера на каждой странице (границы страницы описывает ISO 32000-1 §14.11.2), а аннотации получают печатный флаг, которого ждёт стандарт. Фоллбэк TrimBox — страховочная сетка, а не решение по вёрстке: задание «навылет» требует настоящего trim box внутри большего media box, и руководство по умолчаниям TrimBox, BleedBox и CropBox в PDFlibPas объясняет, как эти боксы задавать и чем может удивить наследование
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]);
// Заменить профиль по умолчанию на реальное печатное условие
if Pdf.LoadOutputIntentProfile(PressProfile, 'DeviceCMYK') <> 1 then
raise Exception.Create('Could not load the CMYK output profile');
// Embed = 1 встраивает полный шрифтовый файл; PDF/X не примет 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); // триадный красный, без RGB
Pdf.DrawText(72, 720, 'Spring catalog, page 1');
Pdf.SaveToFile(OutFile);
finally
Pdf.Free;
end;
end;
Порядок в этом наброске не случаен. LoadOutputIntentProfile заменяет профиль уже существующего output intent, поэтому вызывать его нужно после того, как SetPDFXMode создал intent. Из ничего этот вызов intent тоже не сотворит: на документе без output intent он просто вернёт 0
Почему output intent по умолчанию нужно заменять?
Output intent, который SetPDFXMode добавляет для режимов 1–3, объявляет DeviceCMYK, но ICC-байты за ним — это входящий в комплект библиотеки профиль sRGB с переписанным на 4 числом компонент. Дефолт существует ради обратной совместимости, и его хватает, чтобы файл структурно был PDF/X, однако встроенный профиль не описывает ни одной печатной машины. Preflight-инструмент, который заглянет в заголовок профиля, увидит RGB-профиль с биркой CMYK, а типограф, полагающийся на intent при цветопробе, получит бессмыслицу
Поэтому считайте LoadOutputIntentProfile(FileName, 'DeviceCMYK') обязательным для настоящей печатной работы и передавайте ICC-профиль, который ваш печатный провайдер назвал для этой бумаги и машины. TPDFlib.LoadOutputIntentProfile подменяет поток DestOutputProfile и обновляет /N, остальной словарь intent оставляя как написан. В частности, идентификатор выходного условия и описательные строки дефолтного intent продолжают нести значения в духе sRGB, так что если типография матчится по идентификатору, а не по встроенному профилю, заранее уточните у неё, что именно она проверяет. Это честный пробел дефолтов эпохи 2003 года, а не ошибка вашего кода
Подводный камень первый: версия, которую вы не выбирали
Новый документ PDFlibPas стартует с писательского дефолта PDF 1.5, что выше базы 1.4, которую требуют PDF/X-1a и PDF/X-3. TPDFlib.SetPDFXMode в общем случае решает это молча: если версия сохранения не заблокирована, он опускает версию документа до базы части и продолжает работу. Первая реализация вместо этого отказывала, и каждый SetPDFXMode(1) на свежем документе возвращал 0 — хороший пример правила, верного на бумаге и бесполезного на практике
Отказ остался для единственного случая, где он прав. Если что-то раньше вызвало LockSaveVersion с версией выше базы, SetPDFXMode возвращает 0 с LastErrorCode 601: вызывающий зафиксировал версию намеренно, и библиотека не станет это переспоривать. В общем коде настройки документа, где хелпер блокирует версию по посторонним причинам, именно там X-1a-задание и умирает:
Pdf := TPDFlib.Create;
try
Pdf.LockSaveVersion('1.7'); // выставлен универсальным хелпером «домашних дефолтов»
if Pdf.SetPDFXMode(1) = 0 then
// LastErrorCode = 601: заблокирована 1.7, PDF/X-1a нужна 1.4.
// Для печатных заданий блокировку обходите. PDF/X-4 тут не спасение:
// его база 1.6 тоже ниже блокировки 1.7 и падает так же.
LogError('PDF/X refused, error %d', [Pdf.LastErrorCode]);
finally
Pdf.Free;
end;
Тем же гейтом объясняется смежное ограничение. Режим PDF/X — переключатель создания документа: SetPDFXMode работает на новом документе и возвращает 0 на загруженном из файла, потому что библиотека не может ручаться за контент, который не писала. Конвертация существующего PDF в PDF/X — другая работа, требующая инспекции и починки, а не флага
Подводный камень второй: гейты, отказывающие молча
Пока режим PDF/X активен, PDFlibPas отказывает каждому контентному вызову, запрещённому выбранной частью, в момент самого вызова, а не даёт файлу провалиться на preflight позже. Отказы покрывают заливки и обводки в device RGB (вся X-1a; X-3 и X-4 — если только output intent не RGB), цвета на базе ICC в X-1a, прозрачность и blend modes в X-1a и X-3, optional content в X-1a и X-3, JavaScript-действия, вложенные файлы, фильтр изображений LZW, шрифты Standard 14, которые остались бы невстроенными, и шифрование с открытым ключом. Парольное шифрование через Encrypt на этапе создания не гейтится, но валидатор пометит любой зашифрованный файл, так что держите печатные задания незашифрованными
Чего у отказов нет общего — кода ошибки, и вот это ловушка. Гейты цветовых пространств в SetFillColor, SetLineColor, вызовы цвета на базе ICC и EncryptPublicKey ставят LastErrorCode в 601 — код соответствия библиотеки, которым пользуются и гейты PDF/A. Гейты прозрачности, JavaScript, вложений, LZW и AddStandardFont просто возвращают 0 и не трогают LastErrorCode, а значит, там может ещё сидеть значение от более раннего вызова. Код, который после пачки рисующих вызовов проверяет только LastErrorCode, пропустит половину отказов, поэтому проверяйте каждое возвращаемое значение:
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 в X-1a / X-3
Require(Pdf.AddLinkToJavaScript(72, 72, 120, 20,
'app.alert(1)', 0), 'JavaScript link'); // 0 в любой части
Шрифты заслуживают отдельного предупреждения. AddStandardFont возвращает 0 под PDF/X, потому что библиотека не поставляет контуры Standard 14 и не может их встроить. AddTrueTypeFont устроен иначе: в режиме PDF/A он сам повышает Embed = 0 до полного встраивания, но в режиме PDF/X — нет, поэтому системный шрифт по ссылке попадает в файл, и ловит его только валидатор. Передавайте 1 (полное) или 2 (сабсет) сами или один раз при настройке вызовите SetEmbedAllFonts(1), чтобы случайный 0 не проскочил
Как проверить PDF/X-файл до отправки в типографию?
Прогоните CheckFileCompliance с ComplianceTest = 3 на сохранённом файле. TPDFlib.CheckFileCompliance возвращает 0, если находок не зафиксировано, иначе — хэндл списка строк, элементы которого снабжены префиксом PDFX-. Аудит PDF/X читает файл тем же стриминговым читателем, что и preflight PDF/A и PDF/UA в PDFlibPas, и проверяет правила, которые печатный preflight проверяет в первую очередь:
- Файл не зашифрован, XMP несёт известный
GTS_PDFXVersion, а версия PDF не превышает базу части - Существует ровно один output intent, его подтип —
GTS_PDFX, и он несёт целевой профиль и идентификатор выходного условия - Информационный словарь объявляет
/Trapped - У каждой страницы есть TrimBox внутри MediaBox, а BleedBox, если присутствует, охватывает TrimBox
- У каждого шрифта, использованного на странице, есть встроенная программа, причём Type0 проверяются через их потомка-шрифт
- Нет запрещённых типов аннотаций (RichMedia, Screen, 3D, Movie, Sound, FileAttachment), нет open-действия JavaScript и document-level JavaScript, нет вложенных файлов и нигде в графе объектов нет фильтра LZWDecode
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 // элементы нумеруются с единицы
Report.Add(Pdf.GetStringListItem(ListId, I));
finally
Pdf.ReleaseStringList(ListId);
end;
end;
Отдавайте себе отчёт, что значит этот «прошёл». Валидатор PDFlibPas аудитирует перечисленные выше структурные правила, и именно они ловят ошибки генератора. Это не цвето-точный preflight: он не проверяет суммарное покрытие краской, разрешение изображений и то, совпадает ли встроенный профиль с условием, на котором печатает ваша типография. Используйте его как гейт в сборочном или экспортном конвейере, а проверки, специфичные для печатной машины, оставьте preflight-профилю типографии — его она всё равно прогонит
FAQ
Поддерживает ли PDFlibPas прозрачность PDF/X-4?
Да. В SetPDFXMode(3) PDFlibPas разрешает прозрачность, blend modes и группы optional content и пишет файл PDF 1.6. Те же вызовы возвращают 0 под PDF/X-1a и PDF/X-3
Может ли PDFlibPas конвертировать существующий PDF в PDF/X?
Нет. SetPDFXMode применим только к документам, созданным в текущей сессии TPDFlib, и возвращает 0 для загруженного файла. Любой существующий PDF вы по-прежнему можете аудитировать через CheckFileCompliance с тестом 3
Какой код ошибки возвращает отклонённый вызов PDF/X?
Отказы цветовых пространств и шифрования с открытым ключом ставят LastErrorCode в 601. Прочие гейты — прозрачность, JavaScript, шрифты Standard 14 — лишь возвращают 0, поэтому всегда проверяйте результат функции
Вывод PDF/X, валидатор PDF/X и остальной набор инструментов печати и архивирования — часть PDFlibPas, нативной Object Pascal PDF-библиотеки losLab для Delphi. Издания, платформы и полную справку по API смотрите на странице продукта PDFlibPas Delphi PDF Library