Сшивання сторінок об'єднує вміст існуючих сторінок на спільному полотні, що відрізняється від операції об'єднання файлів. losLab PDF Library реалізує сшивання сторінок через Form XObject та компоновані шаблони водяних знаків для Delphi: методи StitchPageOverlay, StitchPagesSideBySide та StitchPagesVertically розміщують захоплені сторінки в будь-якому масштабі з повною векторною точністю, а TPDFlibCompositeWatermark накладає текстові та графічні водяні знаки на діапазон сторінок за один виклик. Якщо вам потрібно просто додати цілі сторінки з кількох документів в один файл, це робота для методів сімейства злиття, описаних у статті про швидке злиття PDF на байтовому рівні; ця стаття присвячена об'єднанню вмісту сторінок в одному координатному просторі
Запити, які призводять до цього рішення, є буденними, але наполегливими. Наприклад, навчальному закладу потрібно розмістити дві екзаменаційні сторінки на одному альбомному аркуші, щоб удвічі скоротити використання паперу. Фахівцю з локалізації потрібно бачити англійську сторінку та її переклад поруч на одній сторінці зі спільною прокруткою. Юридичній команді потрібно нанести на кожну чернетку три шари водяних знаків одночасно: логотип у кутку, діагональний напис «CONFIDENTIAL» по центру та невеликий колонтитул внизу, і лише на сторінках з 1 по 3. Усі три випадки зводяться до однієї проблеми, а саме: розміщення вже зверстаного вмісту сторінки в іншому місці та в іншому розмірі без погіршення його якості до растрової картинки
Чому звичайне об'єднання потоків вмісту PDF не працює?
Наївне сшивання — тобто просте копіювання потоку /Contents вихідної сторінки на цільову сторінку — зазнає невдачі з трьох структурних причин. По-перше, виникає конфлікт координат: обидва потоки вмісту припускають власну точку відліку в лівому нижньому кутку свого власного MediaBox, оскільки вставлений вміст накладається на існуючий вміст у невідповідному місці. По-друге, виникає конфлікт імен ресурсів: кожна сторінка розпізнає такі оператори, як /F1 Tf та /Im0 Do, за своїм власним словником /Resources, і дві різні сторінки часто пов'язують одне й те саме ім'я з різними шрифтами чи зображеннями, через що одна з них буде відображатися з неправильним ресурсом. По-третє, пряме копіювання потоку не дає масштабування; все, що ви можете зробити, це змістити координати
Об'єкти Form XObject вирішують усі три проблеми одночасно, і саме тому losLab PDF Library базує своє сшивання на них. Стандарт ISO 32000-1 §8.10 визначає об'єкт Form XObject як автономний потік вмісту з власним словником /Resources та власним координатним простором, який поміщається на сторінку шляхом виклику його імені під дією поточної матриці перетворення. Метод CapturePage упаковує всю існуючу сторінку (вміст разом із ресурсами) в об'єкт /Subtype /Form XObject, а функції сшивання потім позиціонують його за допомогою матриці перетворення cm: масштабують, зміщують або повертають на власний розсуд. Оскільки джерело залишається векторним, текст залишається текстом, а контури — контурами при будь-якому масштабі, що забезпечує незалежність від роздільної здатності, аналогічно до того, як описано в статті про векторну графіку та градієнтні заливки. Нічого не раструється, а ізольований словник ресурсів означає, що захоплена сторінка не конфліктуватиме з цільовою сторінкою за ім'я /F1
Як об'єднати дві сторінки PDF на одній сторінці?
StitchPagesSideBySide(Page1, Page2, Gap, TargetHeight) — це пряма відповідь: метод захоплює обидві вихідні сторінки, створює нову ширшу сторінку та розміщує дві захоплені сторінки ліворуч і праворуч із проміжком між ними, масштабуючи кожну під TargetHeight. Властивість повертає номер нової сторінки (починаючи з 1) або 0 у разі помилки, причому обидві вихідні сторінки захоплюються та приховуються в процесі виконання цієї операції. Завдання з двома екзаменаційними сторінками на одному аркуші вирішується всього трьома рядками коду
var
Lib: TPDFlib;
NewPage: Integer;
begin
Lib := TPDFlib.Create;
try
Lib.LoadFromFile('exam.pdf', '');
// Pages 1 and 2 on one new wider page with an 18pt gutter,
// both scaled to a 595pt output height (A4 landscape)
NewPage := Lib.StitchPagesSideBySide(1, 2, 18, 595);
if NewPage > 0 then
Lib.SaveToFile('exam-2up.pdf');
finally
Lib.Free;
end;
end;
Для вертикальної композиції метод StitchPagesVertically(PageRanges, Gap, TargetWidth) об'єднує весь діапазон сторінок в одну довгу сторінку плакатного типу. Параметр PageRanges приймає стандартний синтапис діапазонів, наприклад '1-3,5'. Кожна вихідна сторінка масштабується так, щоб її ширина відповідала TargetWidth, тому внесок кожної сторінки у висоту дорівнює її власній висоті, помноженій на її коефіцієнт масштабування, а загальна висота вихідної сторінки є сумою цих значень плюс проміжки Gap між парами сторінок. Це чудовий варіант для документів безперервного перегляду або довгих квитанцій, зібраних із джерел стандартного розміру
Як накласти одну сторінку на іншу?
Метод StitchPageOverlay(SourcePage, TargetPage, Left, Top, Width, Height, Opacity) малює одну існуючу сторінку на іншій існуючій сторінці замість створення нової. Вихідна сторінка захоплюється як Form XObject і малюється на цільовій сторінці в заданому прямокутнику з непрозорістю Opacity від 0 до 1 (де 1 — повністю непрозоро). Ширина та висота є вільними параметрами, тому зображення може розтягуватися, якщо співвідношення сторін вихідної сторінки відрізняється від заданих розмірів; передавайте зображення пропорційно, якщо хочете отримати неспотворений ескіз вихідної сторінки. Типовий приклад використання — документ, блок затвердження якого ведеться як окрема сторінка та накладається на обкладинку
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.LoadFromFile('report.pdf', '');
// Page 5 holds the approval block; place it on page 1
// in a 260 x 180 box at 45% opacity. Page 5 is consumed:
// it is captured and hidden, so pages 6..n become 5..n-1
if Lib.StitchPageOverlay(5, 1, 300, 500, 260, 180, 0.45) = 1 then
Lib.SaveToFile('report-approved.pdf');
finally
Lib.Free;
end;
end;
Що відбувається з номерами сторінок після захоплення сторінки?
Метод CapturePage поглинає вихідну сторінку: її вміст переноситься в XObject, а сама тепер уже порожня сторінка приховується, тому кожна сторінка з більшим номером відразу зміщується на одну позицію вниз. Це найважливіша семантична деталь у всьому API сшивання сторінок. losLab PDF Library автоматично обробляє цю арифметику всередині кожного виклику зшивання: StitchPageOverlay коригує індекс цільової сторінки, якщо вона знаходиться вище вихідної, а StitchPagesSideBySide спочатку захоплює сторінку з більшим номером, щоб індекс другого захоплення залишався правильним. Однак ваш власний код повинен враховувати це зміщення між викликами. Два правила дозволяють зберегти коректність коду при багаторазовому сшиванні: записуйте будь-які розміри або номери сторінок, які вам знадобляться, до першого захоплення, оскільки вони зміняться після нього; і якщо ви захоплюєте кілька сторінок у циклі самостійно, обробляйте їх у порядку спадання номерів сторінок, щоб приховування наступної сторінки ніколи не змінювало індекс попередньої сторінки, яку ви ще не захопили
Як застосувати багатошаровий водяний знак до діапазону сторінок?
Рівень шаблонів водяних знаків у модулі PDFlibWatermark.pas перетворює принцип «визнач один раз, застосовуй до багатьох сторінок» на об'єкти. Клас TPDFlibWatermarkBase містить загальні властивості для будь-вого водяного знака: позиціонування по дев'яти точках Position (від wpTopLeft до wpBottomRight, плюс wpCustom для використання точних координат X/Y), кут повороту Angle у градусах, непрозорість Opacity від 0 до 1, та рядок PageRange, де порожнє значення означає всі сторінки. Клас TPDFlibTextWatermark додає текст, ID шрифту (типово Helvetica), розмір та RGB-колір; TPDFlibImageWatermark додає ID раніше завантаженого зображення разом із шириною та висотою розміщення. Клас TPDFlibCompositeWatermark містить список вкладених водяних знаків і застосовує їх рекурсивно, а метод TPDFlib.ApplyWatermark є єдиною точкою входу, яка враховує діапазон сторінок шаблону та повертає кількість оброблених сторінок. Тришаровий юридичний штамп реалізується так
var
Lib: TPDFlib;
Logo: TPDFlibImageWatermark;
Diagonal, Footer: TPDFlibTextWatermark;
Stack: TPDFlibCompositeWatermark;
begin
Lib := TPDFlib.Create;
try
Lib.LoadFromFile('draft.pdf', '');
Stack := TPDFlibCompositeWatermark.Create;
try
Logo := TPDFlibImageWatermark.Create;
Logo.ImageID := Lib.AddImageFromFile('logo.png', 0);
Logo.Position := wpTopRight;
Logo.Width := 90;
Logo.Height := 32;
Stack.Add(Logo);
Diagonal := TPDFlibTextWatermark.Create;
Diagonal.Text := 'CONFIDENTIAL';
Diagonal.Position := wpCenter;
Diagonal.Angle := 45;
Diagonal.FontSize := 60;
Diagonal.Opacity := 0.15;
Stack.Add(Diagonal);
Footer := TPDFlibTextWatermark.Create;
Footer.Text := 'Internal review copy';
Footer.Position := wpBottomCenter;
Footer.FontSize := 9;
Footer.Opacity := 0.6;
Stack.Add(Footer);
Stack.PageRange := '1-3'; // children inherit this range
Lib.ApplyWatermark(Stack);
Lib.SaveToFile('draft-marked.pdf');
finally
Stack.Free; // composite frees its children
end;
finally
Lib.Free;
end;
end;
Варто детальніше пояснити дві деталі композиції. По-перше, успадкування діапазону сторінок є одностороннім і неруйнівним: вкладений елемент із порожнім значенням PageRange тимчасово успадковує діапазон композиту під час виклику Apply, тоді як елемент із власним діапазоном зберігає його. Це дозволяє закріпити колонтитул на кожній сторінці, тоді як діагональний штамп буде накладатися лише на розділ чернетки. По-друге, володіння об'єктами слідує за композитом: деструктор TPDFlibCompositeWatermark.Destroy автоматично звільняє свої дочірні об'єкти, тому ви просто знищуєте сам композит, як показано в прикладі вище
Обмеження, про які варто знати
Існують два реальних обмеження, і обидва випливають із принципу роботи самого механізму. По-перше, захоплення сторінки охоплює лише потік вмісту та ресурси. Анотації, посилання, поля форм та коментарі існують як окремі об'єкти в словнику сторінки, а не в потоці вмісту, тому вони не переносяться в XObject. Сшита розмітка містить лише видимий вміст сторінки, але не інтерактивний шар вихідних документів. По-друге, позиціонування тексту по дев'яти точках є приблизним: без повноцінного движка розмітки бібліотека не може знати точну ширину відрендереного рядка, тому вона оцінює текстове поле приблизно в 40% від ширини сторінки при виборі таких позицій, як wpCenter. Цього цілком достатньо для центрування короткої позначки, але для попіксельно точного розміщення довгих рядків перемикайтеся на wpCustom та обчислюйте координати самостійно
Масштаб не є практичним обмеженням, оскільки захоплення та сшивання оперують посиланнями на об'єкти, а не перекодовують вміст сторінки. Якщо розмір вихідних файлів становить сотні мегабайт, перед сшиванням слід застосувати ті самі правила, що описані в посібнику зі злиття та розділення великих файлів із прямим доступом. Описані тут функції сшивання та класи шаблонів водяних знаків постачаються у поточній версії losLab PDF Library для Delphi, C# та VB.NET, разом із низькорівневими примітивами CapturePage, DrawCapturedPage та DrawCapturedPageMatrix для випадків, коли потрібна розмітка, яку не покривають три готові виклики зшивання