Голубая плашка лежит на жёлтом фоне, и графическое состояние, красящее её, несёт /op true и /OPM 1. На экране перекрытие — плоский голубой. На печатном листе то же перекрытие выходит зелёным, потому что RIP не тронул жёлтую секцию и положил голубую поверх. Оба результата следуют файлу буква в букву; экранный рендер просто никогда не спрашивал, что делает с этим флагом устройство воспроизведения, поэтому одобрение на экране прикрывает цвет, который никогда не дойдёт до бумаги
Почему страница печатается иначе, чем предпросматривается?
Наложение — инструкция устройству вывода насчёт секций, а у RGB-экрана секций нет. ISO 32000-1, §8.6.7 определяет, что происходит, когда запись OP (обводка) или op (заливка) ExtGState истинна: красочные компоненты, которые текущая краска не называет, сохраняют то, что уже было на их секции, вместо стирания в ноль. Голубой объект, нарисованный так, не убирает с жёлтой секции ничего. Вьювер, композирующий в RGB, хранить жёлтый негде, поэтому он красит голубым поверх и двигается дальше
Ни один из выводов не сломан. Разногласие встроено в обе модели воспроизведения и остаётся невидимым, пока кто-нибудь не приложит печатную пробу к монитору. Для дизайнера, случайно выставившего чёрному тексту выбивание, или оставившего белый логотип в наложении и ждавшего, что он проявится, это уже поздняя стадия работы
Включение цветопробы через RenderOverprintPreview
HotPDF выставляет цветопробу одним булевым свойством на документе — THotPDF.RenderOverprintPreview, по умолчанию False. Когда оно выставлено, растровый рендер отслеживает триадные секции под каждой накладывающейся краской и применяет правила §8.6.7 перед записью пикселя, поэтому перекрытия показывают ту комбинацию красок, какую выдала бы печатная машина. Оставлять умолчание выключенным правильно для обычного просмотра: читатели офисного PDF ждут, что он выглядит так, как показывает их привычный вьювер. Включайте его на том шаге, чей вывод уходит к предпечатному оператору
var
Pdf: THotPDF;
Device: THPDFBitmapRenderDevice;
Proof: TBitmap;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('cover-cmyk.pdf');
Pdf.RenderOverprintPreview := True; // цветопроба секций, по умолчанию False
Device := THPDFBitmapRenderDevice.Create;
try
if Pdf.RenderLoadedPageToDevice(0, 150, Device) then
begin
Proof := Device.TakeBitmap; // владение переходит к вызывающему коду
try
Image1.Picture.Assign(Proof);
finally
Proof.Free;
end;
end;
finally
Device.Free;
end;
finally
Pdf.Free;
end;
end;
Правила секций зависят от цветового пространства накладывающейся краски, и их стоит знать: каждое объясняет отдельный сюрприз на пробе:
- DeviceCMYK под
/OPM 1: нулевой компонент оставляет свою секцию нетронутой, ненулевой — заменяет. Cyan 100 / yellow 0 поверх жёлтого фона сохраняет жёлтый и даёт зелёный - DeviceGray: краска называет только чёрную секцию, поэтому голубой, пурпурный и жёлтый под ней выживают. Белый DeviceGray-логотип, выставленный в наложение на фоне CMY, потому и исчезает
- Separation и DeviceN: компонент с именем
Cyan,Magenta,YellowилиBlackзаменяет ту триадную секцию напрямую,/Noneне красит вовсе, а/Allпишет каждую триадную секцию - Плашечные компоненты: любое другое имя трактуется как плашечная краска; её tint-преобразование даёт альтернативный цвет, который перемножается по триадным секциям под ней, поэтому плашка затемняет то, что накрывает, вместо замены
Цветопроба применяется к залитым и обведённым путям и к тексту, включая текст в режиме обводки, где решает флаг обводки OP, а не op. Изображения, inline-изображения и sh-затенения продолжают через свои полные растровые цветовые конвейеры и красят с обычной прозрачностью. На практике это покрывает то, что preflight-отчёты мечают чаще всего: правила наложения, ключевые линии, текст и плоские тинты
Прозрачность остаётся живой, пока цветопроба включена. Группа прозрачности с выставленным выбиванием по-прежнему выбивает, и группа композируется в том же проходе, что правила наложения, поэтому печатный файл с прозрачной графикой поверх накладывающегося чёрного фона цветопробит оба эффекта вместе. Получить одно правильно, а другое неправильно — значит выдать другую неправильную пробу
Настройка участвует и в идентичности кешированных рендеров. RenderLoadedPageToBitmapCached ключует свои записи по странице, DPI и варианту, собранному из настроек рендеринга, включающих RenderOverprintPreview, а дисковый ярус использует тот же вариант. Вьювер, скачущий между обычным и цветопробным предпросмотром, получает две отдельные записи кеша и никогда не нуждается в вызове InvalidateRenderedPageCache из-за того, что переключатель сменился
Ловушка /OPM 0 в файлах DeviceCMYK
Вот случай, отправляющий людей искать баг. Файл выставляет /op true на DeviceCMYK-заливке голубым, но оставляет OPM на умолчании 0 — и цветопроба всё ещё показывает голубой, выбивающий жёлтый. Цветопроба права. В режиме наложения 0 §8.6.7 говорит, что DeviceCMYK-краска выставляет все четыре триадных компонента, нули включительно, поэтому нулевой жёлтый компонент заменяет жёлтую секцию в точности как выбивание. Режим наложения 0 защищает только секции, которые краска не называет, а для четырёхкомпонентного CMYK-цвета это плашечные секции и ничего больше
На триадных секциях, которые показывает композитная проба, режим 0 и обычное выбивание дают одни и те же пиксели, поэтому HotPDF красит те DeviceCMYK-объекты как обычные выбивания. Чтобы CMYK-объект накладывался на другие триадные краски, ExtGState, несущий /op true, нуждается ещё и в /OPM 1. Когда проба и замысел дизайнера расходятся на DeviceCMYK-объекте, откройте тот ExtGState и ищите запись OPM, прежде чем подозревать что-либо ещё
Есть и обратный сюрприз. Под /OPM 1 белый CMYK — четыре нуля, а четыре нуля под наложением оставляют каждую секцию как была. Белый текст, выставленный в наложение, исчезает на листе, и цветопроба показывает его исчезновение — в этом весь смысл её запуска
Три устройства за RenderLoadedPageToDevice
THotPDF.RenderLoadedPageToDevice(PageIndex, DPI, Device) — единственная точка входа, чью цель выбирает переданный объект устройства. Она поднимает исключение для nil-устройства, трактует DPI ниже 1 как 150 и возвращает то, что устройство сообщит из своего метода Execute. THPDFRenderDevice — абстрактная база с двумя виртуальными членами: Kind, возвращающим rdkBitmap, rdkDeviceContext или rdkEnhancedMetafile, и Execute(Document, PageIndex, DPI)
Три поставляемых устройства владеют своим выводом по-разному:
THPDFBitmapRenderDeviceдержитTBitmapв свойствеBitmapи освобождает его в деструкторе, еслиTakeBitmapне передал его вам раньшеTHPDFDeviceContextRenderDevice.Create(ADC, AWidth, AHeight)рисует вHDC, который у вас уже есть, и ничем не владеет; конструктор поднимает исключение на нулевом DC или неположительном размере, и фон не красится, поэтому экранный DC нуждается в собственной белой заливкеTHPDFMetafileRenderDeviceдержитTMetafile, покаTakeMetafileего не передаст. Текст и векторные пути остаются масштабируемыми в расширенном метафайле, а image XObject приходят растрированными
Владение следует обычным правилам Delphi. Читайте свойство Bitmap или Metafile, когда объект должен жить лишь столько, сколько устройство; вызывайте метод Take, когда он обязан его пережить. Возьмите растр, освободите устройство, забудьте про растр — и утечка растёт с числом страниц: невидимая на пятистраничном примере, очевидная на пакете в пятьсот страниц. Дайте каждому объекту собственный try/finally — и вопрос «кто что освобождает» ответится сам
Диспетчеризуйте по Kind в коде приложения вместо проверки is THPDFBitmapRenderDevice. Поскольку Execute и Kind — публичные виртуальные методы, вы можете написать собственный класс устройства, и код, проверяющий тип, продолжит работать, когда одно из ваших устройств заменит поставляемое
Почему принтерный рендер приземляется в угол?
Путь контекста устройства размеряет страницу по аргументу DPI, а не по ширине и высоте, которые вы передали. Рендерер масштабирует PDF-единицы на DPI / 72 и использует высоту только чтобы пришвартовать начало страницы к низу поверхности. Зашейте 300 и отправьте задание на принтер 600 dpi — страница выйдет в половину размера, прижатая к левому нижнему углу листа. Разрешение принтера — свойство драйвера, так что спрашивайте его у драйвера:
procedure PrintPage(Pdf: THotPDF; PageIndex: Integer);
var
Device: THPDFDeviceContextRenderDevice;
PrinterDPI, W, H, Tmp: Integer;
L, B, R, T: Single;
begin
if not Pdf.GetLoadedPageVisibleBox(PageIndex, L, B, R, T) then
Exit;
Printer.BeginDoc;
try
// спросите драйвер; 300, 600 и 1200 dpi — все ходовые
PrinterDPI := GetDeviceCaps(Printer.Handle, LOGPIXELSX);
W := Round((R - L) * PrinterDPI / 72);
H := Round((T - B) * PrinterDPI / 72);
if (Pdf.GetLoadedPageRotation(PageIndex) mod 180) = 90 then
begin
Tmp := W; W := H; H := Tmp; // поменять для страниц 90 / 270
end;
Device := THPDFDeviceContextRenderDevice.Create(
Printer.Canvas.Handle, W, H);
try
Pdf.RenderLoadedPageToDevice(PageIndex, PrinterDPI, Device);
finally
Device.Free;
end;
finally
Printer.EndDoc;
end;
end;
Ширина и высота берутся из видимой коробки страницы — CropBox, обрезанного по MediaBox, — в собственном разрешении принтера, и вызывающий меняет их местами для повёрнутых страниц до создания устройства. Это совпадает с тем, чего рендерер ждёт, пришвартовывая начало координат
Цветопроба наложения не идёт этим путём. Устройства контекста устройства и метафайла выпускают GDI-вызовы прямо в цель, а правилам секций нужны пиксели, уже лежащие под каждой краской, — они есть только у растрового рендера. Для бумажной пробы срендерите растровую цветопробу и растяните её на холст принтера. Небольшое самодельное устройство держит это за той же точкой входа:
type
// собственное устройство: растровая цветопроба, затем растяжка на принтер
TPrinterProofDevice = class(THPDFRenderDevice)
public
function Kind: THPDFRenderDeviceKind; override;
function Execute(Document: THotPDF; PageIndex, DPI: Integer): boolean;
override;
end;
function TPrinterProofDevice.Kind: THPDFRenderDeviceKind;
begin
Result := rdkDeviceContext;
end;
function TPrinterProofDevice.Execute(Document: THotPDF;
PageIndex, DPI: Integer): boolean;
var
Proof: TBitmap;
OldProof: Boolean;
begin
OldProof := Document.RenderOverprintPreview;
Document.RenderOverprintPreview := True;
try
Proof := Document.RenderLoadedPageToBitmap(PageIndex, DPI);
finally
Document.RenderOverprintPreview := OldProof; // вернуть настройку вызывающего как была
end;
Result := Proof <> nil;
if Result then
try
Printer.Canvas.StretchDraw(
Rect(0, 0, Printer.PageWidth, Printer.PageHeight), Proof);
finally
Proof.Free;
end;
end;
Вызывайте его между Printer.BeginDoc и Printer.EndDoc с DPI, дающим растру достаточно пикселей на лист, — около 200–300 для пробы формата letter. Прямоугольник растяжки выше заполняет печатную область; держите в нём аспектное отношение страницы, если страница и бумага различаются формой
Куда цветопроба встаёт рядом с preflight
Цветопроба отвечает на один вопрос: какие краски выживают там, где объекты перекрываются. Она композируется на том же 24-битном RGB-растре, что и любой другой рендер, поэтому её цвета показывают комбинацию красок, а не измеренный цвет листа. Условия печатной машины — вопрос цветоуправления, и документ обходится с ними через ConfigureRenderICCWorkflow, принимающий профиль пробы, выходной профиль и флаг компенсации точки чёрного, и ClearRenderICCWorkflow, чтобы сбросить их снова
Самое продуктивное место для цветопробного рендера — рядом с проверками, которые вы уже гоняете. Проход preflight рапортует, что чёрный текст выставлен в наложение; цветопробный рендер показывает оператору, что это делает на странице; оба попадают в один отчёт. Упаковочная работа любит мешать наложение с плашечными красками, и разбор статьи о отрисовке плашечных цветов Separation и DeviceN покрывает красочную сторону той же страницы, включая односекционные просмотры через RenderLoadedPageSeparationPlate. Статьи о рендере страницы PDF в растр и о печати загруженного PDF через TPrinter описывают обе цели устройств в их обычной форме
Всё это работает в нативном коде Delphi и C++Builder без DLL рендеринга, поставляемой рядом с исполняемым файлом. На странице компонента HotPDF для Delphi перечислены возможности рендеринга и предпечатной подготовки, а пробная сборка позволяет прогнать эти примеры на ваших собственных печатных файлах