Сливането и разделянето са двете операции със страници, към които всеки посяга най-напред, и те покриват голяма част от задачите. Те обаче не покриват всичко. Има отделна група от задачи, която пренарежда страници, вместо да мести цели файлове: разполагане на четири слайда върху един лист за брошура, влачене на страница от задната част на документа към предната или извличане на страници 3, 7 и 12 в кратък откъс, без да се засяга останалата част. PDFium предлага три метода точно за това и всеки от тях се държи различно от сливането и разделянето, които вече познавате. Тази статия разглежда какво правят те, къде се намират изходните точки и един детайл за собствеността, който е причинявал срив в практиката.
Трите метода са ImportNPagesToOne за N-up импозиция, MovePages за пренареждане на място и ImportPagesByIndex за извличане на подмножество. Сливането подрежда документи край до край и оставя броя на страниците равен на сумата от входните данни. Разделянето записва няколко изходни файла от един входен. Трите операции тук се намират по средата: една от тях променя броя на източните страници, споделящи един лист, една от тях променя реда в рамките на един документ и една от тях копира избран набор от страници в друг документ. Знаейки кой за какво е, ви спестява принудителното сливане и изтриване, където едно извикване би свършило работа.
Какво всъщност прави N-up импозицията
Импозицията е предпечатният термин за подреждане на няколко страници източници върху един по-голям лист, така че отпечатаният и сгънат резултат да се чете в правилния ред. Ежедневната версия е раздаването от 2 страници, тетрадката от 4 страници или страница с контакти, която събира дузина миниатюри на една страница. PDFium управлява геометрията чрез едно извикване:
function ImportNPagesToOne(
OutputWidth, OutputHeight: Single;
NumX, NumY : Cardinal): TPdf;
NumX и NumY описват мрежата. Стойност 2, 1 поставя две изходни страници една до друга; 2, 2 събира четири в квадрантно оформление; 4, 3 изгражда страница с дванадесет миниатюри. PDFium чете страниците източници по ред, мащабира всяка една надолу, за да се побере в клетката ѝ, и запълва мрежата отляво надясно, отгоре надолу, като стартира нов изходен лист, винаги когато текущата мрежа се запълни. Страниците източници не се променят. Това, което получавате обратно, е нов документ, чиито страници са съставени.
Изходният размер е в точки, а не в пиксели
OutputWidth и OutputHeight са PDF потребителски единици, а PDF потребителската единица е една точка, което е една седемдесет и втора от инча. Единицата декларира физическия размер на изходния лист и няма нищо общо с екранните пиксели или DPI за изобразяване. Това е най-често срещаното място за грешка при импозиция, тъй като разработчик, свикнал с растерни изображения, посяга към броя на пикселите и завършва с лист с размер на пощенска марка или билборд.
Числата, които си струва да запомните, са двата размера на страниците, които ще използвате най-често. US Letter е 612 на 792 точки, тъй като 8.5 инча по 72 е 612, а 11 инча по 72 е 792. A4 е приблизително 595 на 842 точки от размерите му 210 на 297 милиметра. Собствената заглавна част на обвивката ясно посочва правилото, че една единица е една седемдесет и втора от инча, а модулът предоставя константа PointsPerInch, равна на 72, ако предпочитате да изчислите размер от инчове в кода, вместо да пишете литерал.
const
LetterW = 612.0; // 8.5 in * 72
LetterH = 792.0; // 11 in * 72
var
Source, Composite: TPdf;
begin
Source := TPdf.Create(nil);
Composite := nil;
try
Source.FileName := 'slides.pdf';
Source.Active := True;
// Four source pages per Letter sheet, 2 by 2 grid.
Composite := Source.ImportNPagesToOne(LetterW, LetterH, 2, 2);
if Composite = nil then
raise Exception.Create('PDFium rejected the imposition arguments');
Composite.SaveAs('slides-4up.pdf');
finally
Composite.Free; // see the next section: this is mandatory
Source.Free;
end;
end;
Върнатият дескриптор трябва да бъде освободен от вас
Прочетете сигнатурата отново. ImportNPagesToOne връща TPdf, а не Boolean. Тази върната стойност е съвсем нов дескриптор на документ, разпределен отделно от източника, и извикващият го притежава. Обектът източник TPdf, върху който сте извикали метода, е недокоснат и все още притежава собствения си дескриптор; съставеният обект е втори, независим обект. Ако оставите върнатия TPdf да излезе от обхват, без да го освободите, ще изтече цял PDFium документ.
По-опасната грешка работи в другата посока. Под капака методът изисква от PDFium нов FPDF_DOCUMENT чрез FPDF_ImportNPagesToOne, след което обвива този необработен дескриптор в върнатия TPdf, така че жизненият цикъл на обвивката да управлява дескриптора. От този момент нататък има точно един собственик на дескриптора и точно едно място, където той трябва да бъде затворен: когато освободите (Free) върнатия обект. Невнимателен път на грешка, който едновременно освобождава обвивката и извиква FPDF_CloseDocument върху необработения дескриптор, който е уловил, затваря същия PDFium документ два пъти. Това е двойно освобождаване и е конкретният бъг, който навреди на потребител тук веднъж. Правилото, което го предотвратява, е кратко. Затваряйте документа само по един път, като освободите TPdf, който методът ви е предал, и никога не посягайте зад обвивката, за да затворите вече приетия дескриптор.
От това произтичат две следствия. Първо, методът връща nil, когато PDFium отхвърли аргументите, като например нула на някоя от осите на мрежата или срив при разпределяне, така че проверката за nil е задължителна, преди да докоснете резултата. Второ, инициализирайте изходната си променлива с nil преди блока try и я освободете в finally, както прави примерът по-горе, така че срив по средата да не може да ви остави да освобождавате недефинирана референция или да пропуснете освобождаването изцяло.
Пренареждане на страници без повторното им записване
Импозицията изгражда нов документ. Пренареждане променя един документ на място. Методът MovePages изважда набор от страници от текущите им позиции и ги поставя на местоназначението, премествайки всичко останало около преместения блок, така че броят на страниците да остане същият:
function MovePages(
const PageIndices: array of Integer;
DestPageIndex : Integer): Boolean;
Индексите са базирани на нула. PageIndices изброява страниците за преместване в реда, в който трябва да завършат, а DestPageIndex е индексът, на който попада първата преместена страница, след като преместването приключи. Тъй като PDFium премества страниците, вместо да копира и прекомпресира съдържанието им, операцията е евтина и без загуба на данни: обектите на страниците запазват своите потоци, своите ресурси и своята автентичност. Това е извикването зад панел за пренареждане чрез плъзгане, където потребителят премества миниатюра в нов слот и вие потвърждавате новия ред с едно преместване. То връща False, когато индексът е извън диапазона, така че валидирайте резултата, вместо да приемате, че пренареждането е извършено.
var
Doc: TPdf;
begin
Doc := TPdf.Create(nil);
try
Doc.FileName := 'report.pdf';
Doc.Active := True;
// Move the last page (index 4 in a 5-page file) to the very front.
if not Doc.MovePages([4], 0) then
raise Exception.Create('MovePages rejected the index');
Doc.SaveAs('report-reordered.pdf');
finally
Doc.Free;
end;
end;
Извлечане на подмножество по индекс
Третата операция копира изричен набор от страници от един документ в друг. ImportPagesByIndex приема документа източник и масив от индекси, базирани на нула, и вмъква тези страници в целта на избрана позиция:
function ImportPagesByIndex(
Source : TPdf;
const PageIndices: array of Integer;
InsertAt : Integer= 0): Boolean;
Извиквате го върху целевия документ и предавате източника като първи аргумент. PageIndices указва кои страници от източника да бъдат извлечени, в реда, в който ги искате; InsertAt е базираният на нула слот в целта, където отива първата импортирана страница, така че 0 ги поставя преди съществуващата първа страница, а текущият брой страници на целта се добавя в края. Празен масив импортира всяка страница, което прави извикването пълно копие, когато имате нужда от такова. То връща False, ако някой индекс е извън диапазона в източника.
Тук разликата с разделянето е важна. Разделянето записва отделни файлове, като една операция произвежда много изходни резултати на диска. ImportPagesByIndex извършва противоположната работа: събира избран набор от страници в един целев документ в паметта, който след това записвате веднъж. Когато задачата е „дайте ми страници 3, 7 и 12 като един кратък PDF“, това е директният път и под капака той обвива FPDF_ImportPagesByIndex.
var
Source, Excerpt: TPdf;
begin
Source := TPdf.Create(nil);
Excerpt := TPdf.Create(nil);
try
Source.FileName := 'manual.pdf';
Source.Active := True;
Excerpt.CreateDocument; // start an empty target
// Pull pages 3, 7 and 12 (zero-based 2, 6, 11) into the excerpt.
if not Excerpt.ImportPagesByIndex(Source, [2, 6, 11], 0) then
raise Exception.Create('A requested page index is out of range');
Excerpt.SaveAs('manual-excerpt.pdf');
finally
Excerpt.Free;
Source.Free;
end;
end;
Чисто сглобяване
Формата от край до край е една и съща и при трите: отворете източника чрез задаване на FileName и превключване на Active в True, изпълнете операцията, запишете със SaveAs и освободете това, което притежавате. Единственият клон, който изисква внимание, е кои извиквания разпределят нов документ. MovePages променя документа, който вече държите, така че има един обект за освобождаване. ImportPagesByIndex записва в цел, която сте създали сами, така че освобождавате източника и целта, която сте отворили. ImportNPagesToOne е изключението, тъй като новият документ е върнатата стойност на метода, а не нещо, което сте конструирали, и за бърз преглед – забравянето, че това е отделен дескриптор, притежаван от извикващия, е начинът, по който се случват както изтичането, така и двойното освобождаване. Инициализирайте резултата с nil, проверете го след извикването и го освободете по един-единствен път.
Ако задачата, която действително имате, е комбиниране на цели файлове, а не пренареждане на страници, вижте сливане на няколко PDF файла в един документ. Ако е обратното – разделяне на един документ на няколко файла, вижте разделяне на PDF документи на няколко файла. Методите за импозиция и пренареждане, описани тук, се доставят като част от PDFium Component за Delphi и C++Builder, заедно с API за зареждане, изобразяване и редактиране, разгледани на други места в този блог.