Поставянето на страници в рамка (page stitching) съвместява съдържанието на съществуващи страници върху общо платно, което е операция, различна от обединяването на файлове. losLab PDF Library реализира свързване на страници чрез Form XObject и модулни шаблони за водни знаци за Delphi: StitchPageOverlay, StitchPagesSideBySide, и StitchPagesVertically поставят уловените страници във всякакъв мащаб с пълна векторна точност, а TPDFlibCompositeWatermark натрупва текстови и графични водни знаци в диапазон от страници с едно извикване. Ако това, което наистина ви трябва, е да добавите цели страници от няколко документа в един файл, тогава се нуждаете от функциите за сливане (merge), разгледани в статията за бързо обединяване на PDF на байтово ниво; тази статия е за комбиниране на съдържанието на страниците в рамките на едно координатно пространство
Заявките, които водят дотук, са ежедневни и постоянни. Дадено училище иска да разположи две страници с изпитни задачи върху един хоризонтален лист, за да спести хартия. Редактор по локализация иска да вижда английската страница и нейния превод една до друга на една страница, които да се превъртат заедно. Юридически екип иска всеки чернови документ да бъде подпечатан с три слоя едновременно: лого в ъгъла, диагонален надпис ПОВЕРИТЕЛНО (CONFIDENTIAL) в средата и малък долен колонтитул само на страници от 1 до 3. И трите казуса са свързани с един и същ основен проблем, а именно поставянето на вече форматирано съдържание на страницата на друго място и в друг размер, без да се влошава качеството му до растерно изображение
Защо конкатенирането на PDF потоци от съдържание се чупи?
Наивното свързване – копирането на потока /Contents на изходната страница в целевата страница – се проваля поради три структурни причини. Първо, координатите се сблъскват: и двата потока от съдържание приемат своя собствена начална точка в долния ляв ъгъл на собствения си MediaBox, така че поставеното съдържание попада върху съществуващото съдържание на грешното място. Второ, имената на ресурсите се сблъскват: всяка страница разрешава оператори като /F1 Tf и /Im0 Do спрямо собствения си речник /Resources, а две страници обикновено обвързват едно и също име с различни шрифтове или изображения, така че едната от тях безшумно се визуализира с грешния ресурс. Трето, суровото копиране на поток не ви дава никакво мащабиране; най-доброто, което можете да направите, е транслация
Form XObjects решават и трите проблема наведнъж, и затова losLab PDF Library изгражда свързването си върху тях. ISO 32000-1 §8.10 дефинира Form XObject като самостоятелен поток от съдържание със собствен речник /Resources и собствено координатно пространство, поставен в страница чрез извикване на името му под каквато и да е активна матрица на трансформация. CapturePage обвива цяла съществуваща страница – съдържание плюс ресурси – в XObject от тип /Subtype /Form, а функциите за свързване след това го позиционират с cm матрица: мащабиране, транслация, завъртане по желание. Тъй като източникът остава векторен, текстът остава текст, а пътеките остават пътеки при всяко ниво на мащабиране – същото свойство, което прави векторните графики и шаблоните за засенчване независими от разделителната способност. Нищо не се растеризира, а изолираният речник с ресурси означава, че уловената страница не може да се сблъска с целевата страница за името /F1
Как да комбинирате две PDF страници в една страница?
StitchPagesSideBySide(Page1, Page2, Gap, TargetHeight) е директният отговор: функцията улавя двете изходни страници, създава нова по-широка страница и поставя двете уловени страници отляво и отдясно с разстояние (gutter) между тях, като всяка се мащабира, за да пасне на TargetHeight. Функцията връща новия номер на страницата (1-базиран) или 0 при неуспех, като и двете изходни страници се улавят и скриват като част от операцията. Изпитният казус с две страници на лист се решава с три реда код
var
NextPage: TPDFlibViewerNextPage;
FitWidth: TPDFlibViewerFitWidth;
HandMode: TPDFlibViewerHandMode;
begin
NextPage := TPDFlibViewerNextPage.Create(Self);
NextPage.ActionList := ActionList1;
NextPage.Viewer := FViewer;
btnNext.Action := NextPage;
FitWidth := TPDFlibViewerFitWidth.Create(Self);
FitWidth.ActionList := ActionList1;
FitWidth.Viewer := FViewer;
btnFitWidth.Action := FitWidth;
HandMode := TPDFlibViewerHandMode.Create(Self);
HandMode.ActionList := ActionList1;
HandMode.Viewer := FViewer;
btnHand.Action := HandMode;
end;
За вертикално композиране, StitchPagesVertically(PageRanges, Gap, TargetWidth) подрежда цял диапазон от страници в една висока страница тип постер. PageRanges приема стандартния синтаксис за диапазон като '1-3,5'. Всяка изходна страница се мащабира така, че ширината ѝ да съвпада с TargetWidth, така че височинният принос на всяка страница е нейната собствена височина, умножена по нейния коефициент на мащабиране, а крайната височина на страницата е сумата от тях плюс едно разстояние (Gap) между всяка двойка. Това е формата, която искате за документи с непрекъснато превъртане за преглед или дълги касови бележки, сглобени от източници с формат Letter
Как да наложите една страница върху друга?
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 добавя текст, идентификатор на шрифт (по подразбиране Helvetica), размер и RGB цвят; TPDFlibImageWatermark добавя идентификатор на изображение от предварително добавена картинка плюс ширина и височина за разполагане. 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, когато имате нужда от оформление, което трите готови извиквания за свързване не покриват