PDFium Component излага сливането на PDF файлове чрез един единствен метод: ImportPages. Моделът винаги е един и същ: създайте празен целеви документ, отворете всеки файл източник, извикайте ImportPages, за да копирате страниците, затворете източника и повторете. Когато цикълът приключи, SaveAs записва резултата на диск. Няма специален режим на сливане, няма конфигурация за превключване. Сложността живее в граничните случаи (edge cases) и има няколко, които хапят без предупреждение
Основният цикъл
Две инстанции на TPdf са всичко, от което се нуждаете. Едната държи целевия документ, създаден празен с CreateDocument. Другата отваря всеки файл източник подред. По-долу е дадена процедура, която приема списък с пътища към файлове и записва слетия изход към един път:
procedure MergeFiles(const FileList: TStrings; const OutputPath: string);
var
PdfDest, PdfSrc: TPdf;
InsertAt, I: Integer;
begin
PdfDest := TPdf.Create(nil);
PdfSrc := TPdf.Create(nil);
try
PdfDest.CreateDocument;
InsertAt := 1; // ImportPages използва 1-базирана целева позиция
for I := 0 to FileList.Count - 1 do
begin
PdfSrc.FileName := FileList[I];
PdfSrc.Active := True;
if not PdfSrc.Active then
raise Exception.CreateFmt('Cannot open: %s', [FileList[I]]);
PdfDest.ImportPages(
PdfSrc,
'1-' + IntToStr(PdfSrc.PageCount), // пълен диапазон на документа
InsertAt);
Inc(InsertAt, PdfSrc.PageCount);
PdfSrc.Active := False;
end;
PdfDest.SaveAs(OutputPath);
finally
PdfSrc.Free;
PdfDest.Free;
end;
end;
Две неща в този код е лесно да бъдат пренебрегнати при първо четене. Първото е как PDFium отчита неуспехи при зареждане. Active := True никога не хвърля изключение (never raises an exception): ако файлът липсва, повреден е или е защитен с парола, PDFium хваща грешката вътрешно и оставя Active на False. Без изричната проверка на ред 10, лош файл тихо би отпаднал от сливането без индикация в изхода. Крайният PDF би имал по-малко страници от очакваното и нямаше да знаете кой файл е бил виновникът
Второто е броячът InsertAt. Третият аргумент на ImportPages е 1-базираната позиция в целта, където се приземява първата импортирана страница. Започването от 1 поставя първия документ източник в началото на иначе празен файл. След всеки източник броячът напредва с PdfSrc.PageCount, така че следващата партида страници се добавя след последната. Забравете да го увеличите и всеки последващ източник презаписва страници на позиция 1, давайки ви последния документ в списъка и нищо друго
Селективни диапазони от страници
Не сте длъжни да вземате всяка страница от източника. Низът за диапазон, предаден като втори аргумент, следва прост формат със запетаи и тирета: "1-3" взема страници от 1 до 3, "2,4,6" избира три конкретни страници, а "1-" означава страница 1 до края на документа. Диапазоните могат да се комбинират в един низ, така че "1-3,5,7-" пропуска страници 4 и 6. Една тънкост има значение тук: числата винаги се отнасят за страници в документа източник, започвайки от 1, независимо къде тези страници се озовават в целта. Ако искате страници от 40 до 50 от каталог от 200 страници, низът за диапазон е "40-50", а не позиция спрямо това, което вече е в целта
// Извличане на корицата плюс резюме от три страници (executive summary) от дълъг доклад
PdfSrc.FileName := 'annual-report.pdf';
PdfSrc.Active := True;
if PdfSrc.Active then
begin
// Страница 1 е корицата; страници 3-5 са резюмето
PdfDest.ImportPages(PdfSrc, '1,3-5', InsertAt);
Inc(InsertAt, 4); // 1 корица + 3 страници резюме = 4 добавени страници
PdfSrc.Active := False;
end;
Когато изчислявате увеличението на InsertAt, бройте страниците, които реално сте импортирали, а не броя на страниците в източника. Ако подадете '1,3-5', сте импортирали 4 страници, така че напреднете с 4. Напредването с PdfSrc.PageCount би оставило празнина от празни целеви позиции и би поставило следващия документ източник по-навътре във файла, отколкото е предвидено
Какво запазва ImportPages и какво не
Страниците, копирани от ImportPages, носят своето видимо съдържание непокътнато. Текст, векторна графика, растерни изображения, вградени шрифтове и XObjects на формуляри се прехвърлят като част от потоците със съдържание на страницата. Анотациите на ниво страница, включително коментари, откроявания и мастилени щрихи, също се прехвърлят, защото се съхраняват в речника на страницата, а не на ниво документ
Метаданните на ниво документ са различна история. Низовете за заглавие, автор, тема и ключови думи в Info речника на източника остават назад. Целевият документ започва с празни метаданни след CreateDocument, така че ако слетият изход се нуждае от попълнени тези полета, трябва да ги зададете директно на PdfDest, преди да извикате SaveAs. Свойствата Title, Author, Subject, Keywords и Creator на TPdf приемат обикновени низове (plain strings) и пишат в Info речника при запис
Интерактивните полета на формуляри са по-сложни. Дефинициите на AcroForm полета живеят в речник на ниво документ, а не вътре в отделни потоци от страници. Когато ImportPages копира страница, която съдържа полета на формуляри, визуалният външен вид на тези полета се прехвърля, защото се рендира в потока със съдържание на страницата, но полевите джаджи (field widgets), които ги правят интерактивни, са част от структурата на AcroForm и не следват. В типично сливане, текстово поле от документ източник ще покаже стойността, която е имало към момента на импортиране, но няма да може да се редактира в слетия файл. Ако имате нужда полетата да останат запълняеми, сплескайте ги (flatten them) във всеки документ източник преди импортиране: това изпича текущите стойности в потока със съдържание и премахва интерактивния слой, давайки ви чист визуален резултат без счупени джаджи в изхода
Криптирани файлове източник
Защитените с парола документи източник се отварят по същия начин като некриптираните, с едно допълнително свойство, което трябва да се зададе първо. Присвоете паролата на PdfSrc.Password преди превключване на Active := True и PDFium ще я използва по време на отварянето:
PdfSrc.Password := 'user-password';
PdfSrc.FileName := 'protected.pdf';
PdfSrc.Active := True;
if not PdfSrc.Active then
raise Exception.Create('Wrong password or file cannot be opened');
PdfDest.ImportPages(PdfSrc, '1-' + IntToStr(PdfSrc.PageCount), InsertAt);
Inc(InsertAt, PdfSrc.PageCount);
PdfSrc.Active := False;
Грешна парола води до същия тих Active = False резултат като липсващ файл, така че изричната проверка е също толкова необходима и тук. Криптирането не се прехвърля към целта: страници, импортирани от защитен източник, се приземяват в целта като незащитено съдържание. Ако слетият изход също се нуждае от криптиране, конфигурирайте го на PdfDest, преди да извикате SaveAs
Записване на резултата
SaveAs на TPdf приема или път към файл, или TStream. За повечето сливания претоварването за файл (file overload) е това, което искате:
PdfDest.SaveAs('merged-output.pdf');
Опционалният втори аргумент е TSaveOption, който контролира режима на запазване. По подразбиране, saNone, записва инкрементална актуализация (incremental update), ако документът е бил зареден от файл, или пълно пренаписване (complete rewrite), ако е бил създаден наново. Тъй като цел, изградена с CreateDocument, е винаги нова (fresh), изходът ще бъде компактен файл с една ревизия (single-revision file). Третият аргумент, TPdfVersion, ви позволява да фиксирате хедъра на PDF версията, когато имате потребители надолу по веригата, които изискват специфична версия; оставянето му на pvUnknown позволява на PDFium да избере въз основа на съдържанието
Методите ImportPages и SaveAs, показани тук, са част от PDFium Component за Delphi и C++Builder