Два документа, отворени едновременно, един и същ номер на страница, всеки в собствен панел с възможност за превъртане: това е сърцевината на програма за преглед за сравнение (comparison viewer). PDFium Component доставя това чрез ясен обектен модел (object model), при който TPdf притежава файла, а TPdfView притежава дисплея. Един документ, един TPdf, един TPdfView. Искате три панела, имате три двойки. Трудните части не са извикванията на API; те са аритметиката на оформлението (layout), когато прозорецът променя размера си, и логиката за синхронизиране на страниците, когато решавате кой изглед кой трябва да следва
Оформление на формата (Form Layout)
VCL формата съдържа три TScrollBox контейнера един до друг, всеки с TPdfView вътре и подравнен към alClient, така че да запълва кутията. Два компонента TSplitter стоят между кутиите, така че потребителят да може да регулира ширините на колоните по време на изпълнение. Лента с инструменти (toolbar) над панелите носи бутоните за отваряне, контролите за мащабиране и превключвателя между два/три изгледа
Режимът с три изгледа е булева стойност, която формата проследява вътрешно. Когато се превключи, преизчислявате ширините и показвате или скривате третата колона. Най-простият подход е да изчистите всички свойства Align, да скриете сплитерите (splitters), след което да зададете абсолютни позиции:
procedure TFormMain.UpdateLayout;
var
TotalWidth: Integer;
begin
TotalWidth := ClientWidth;
if ThreeViewMode then
begin
ScrollBox3.Visible := True;
ScrollBox1.Left := 0;
ScrollBox1.Width := TotalWidth div 3;
ScrollBox2.Left := ScrollBox1.Width;
ScrollBox2.Width := TotalWidth div 3;
ScrollBox3.Left := ScrollBox2.Left + ScrollBox2.Width;
ScrollBox3.Width := TotalWidth - ScrollBox3.Left;
// Приложете същата (ClientHeight - височина на лентата с инструменти) към всичките три стойности на Height
end
else
begin
ScrollBox3.Visible := False;
ScrollBox1.Left := 0;
ScrollBox1.Width := TotalWidth div 2;
ScrollBox2.Left := ScrollBox1.Width;
ScrollBox2.Width := TotalWidth - ScrollBox2.Left;
end;
end;
Задаването на Align := alNone и на трите кутии преди аритметиката с цели числа избягва механизма за ограничения на VCL (constraint engine) да се бори с вашите присвоявания. Възстановете видимостта на сплитера след позициониране, ако искате преоразмеряване с плъзгане (drag-to-resize) в режим на два изгледа
Височината на всяка кутия за превъртане е клиентската област (client area) минус височината на панела с инструменти. Тъй като лентата с инструменти е закачена отгоре с alTop, ClientHeight - PanelButtons.Height ви дава използваемото вертикално пространство. Присвоете това на всичките три кутии в рамките на едно и също извикване на UpdateLayout, така че никога да няма кадър, в който една кутия е по-висока от другите и причинява трептене на оформлението (layout flicker)
Отваряне на документ
Всяка двойка панели се нуждае от собствена процедура за отваряне. Моделът е кратък: деактивирайте компонента, задайте името на файла, активирайте, след това проверете Active; ако остане False, поискайте парола и опитайте отново. Имайте предвид, че TPdfView.Active е това, което контролира рендирането, но TPdf.Active е това, което действително отваря файла; те са независими. Задаването на PdfView.Active := True, когато свързаният с него TPdf все още не е активен, е безобидно, но не показва нищо
procedure TFormMain.OpenPdfFile(PdfComponent: TPdf;
PdfViewComponent: TPdfView);
var
Password: string;
begin
if not OpenDialog.Execute then
Exit;
PdfComponent.Active := False;
PdfComponent.FileName := OpenDialog.FileName;
PdfComponent.Password := '';
PdfComponent.Active := True;
// Неуспехите при зареждане са тихи: Active остава False, вместо да хвърля грешка.
if not PdfComponent.Active then
begin
// Най-вероятно защитен с парола файл; дайте на потребителя един повторен опит.
if InputQuery('Password', 'Enter document password:', Password) then
begin
PdfComponent.Password := Password;
PdfComponent.Active := True;
end;
end;
if not PdfComponent.Active then
begin
ShowMessage('Could not open ' + OpenDialog.FileName +
' (повреден файл или грешна парола)');
Exit;
end;
PdfViewComponent.PageNumber := 1;
SetActivePdfView(PdfViewComponent);
end;
Винаги проверявайте PdfComponent.Active след присвояването; повреден файл или грешна парола кара зареждането да се провали тихо, без да се хвърля изключение в пътя по подразбиране. Изричното задаване на PdfViewComponent.PageNumber := 1 след успешно отваряне избягва остарял (stale) номер на страница от предишния документ
Диалогът за съобщение в края е умишлен: вие искате повредените или неподдържани файлове да излязат на повърхността незабавно, вместо да бъдат погълнати като тих празен панел. Потребител, който не вижда нищо, няма представа дали файлът се е заредил и е просто празен, или компонентът го е отхвърлил. Съобщаването на неуспеха запазва грешката видима
Проследяване на активния панел
Когато потребителят щракне вътре в панел, този панел става активен. Формата проследява частно поле FActivePdfView: TPdfView. Визуалната обратна връзка е промяна на цвета на границата на съдържащия TScrollBox: задайте го на clHighlight за активния и clWindow за другите. Свържете това към всеки TPdfView.OnClick и към процедурата за отваряне, така че фокусът да следва документа, който току-що сте отворили
Някои операции се прилагат към всички видими панели, а не само към активния. Булева стойност FAllViewsMode във формата задвижва този клон. Когато е вярна, промените в мащабирането и навигацията по страниците се разпространяват (fan out) до всеки панел, който има активен документ:
procedure TFormMain.ApplyZoomToAll(NewZoom: Double);
begin
if PdfView1.Active then PdfView1.Zoom := NewZoom;
if PdfView2.Active then PdfView2.Zoom := NewZoom;
if ThreeViewMode and PdfView3.Active then PdfView3.Zoom := NewZoom;
end;
Синхронизирана навигация по страниците
Синхронизираната навигация е по избор, но е полезна за работни процеси за ревизия (revision) на документи, където и двата файла покриват един и същ диапазон от страници. Логиката принадлежи към манипулатор на събития (event handler), който се задейства, след като потребителят навигира в един изглед. Когато изглед източник промени своя PageNumber, манипулаторът разпространява този номер към другите изгледи, при условие на един предпазител: целевият изглед трябва да има поне толкова страници, в противен случай пропуснете
PageNumber в TPdfView и в TPdf са независими. TPdf.PageNumber проследява коя страница документният компонент счита за текуща; TPdfView.PageNumber проследява какво се показва на екрана. За целите на навигацията искате свойството на изгледа, а не свойството на документа
Квадратче за отметка с етикет като "Синхронизиране на страници" дава контрол на потребителя. Когато не е отметнато, всеки панел се навигира независимо и манипулаторът излиза незабавно. Тази независимост е важна за случаи на употреба, при които двата документа имат различен брой страници или когато потребителят иска да намери еквивалентния пасаж в превод, който започва на различна страница. Принудителното синхронизиране винаги би направило инструмента по-трудeн за използване от простото подреждане на работния плот (desktop arrangement) с два прозореца
Едно нещо, за което да внимавате: програмното задаване на PdfView.PageNumber в манипулатора за синхронизация само по себе си ще задейства събитието за промяна в този изглед. Предпазете се от безкрайна рекурсия с булев флаг, който задавате преди присвояването и изчиствате веднага след това. Флагът е за форма (per-form), а не за изглед, защото всичките три изгледа споделят един и същ манипулатор
Мащабиране за всеки панел
Всеки TPdfView носи собствено свойство Zoom, Double в проценти, където Zoom := 100 означава действителен размер (100%). Задаването му отменя (overrides) всеки активен FitMode. За бутон за побиране по ширина (fit-to-width) на активния панел, прочетете подходящото мащабиране от PdfView.PageWidthZoom[PdfView.PageNumber] и го присвоете. За побиране на страница (fit-to-page) използвайте PageZoom[PageNumber]. И двете са свойства на масиви (array properties), индексирани по базиран на 1 номер на страница, така че се предпазете от нулев номер на страница, преди да получите достъп до тях
Когато експортирате текущата страница в изображение, прочетете ротацията от изгледа, но извикайте RenderPage в компонента TPdf, а не в изгледа. Формата на растерно изображение (bitmap) на TPdf.RenderPage приема изрични размери в пиксели плюс стойност TRotation и набор от TRenderOptions. Вариантът на функцията връща притежаван от извикващия TBitmap, който вие сами освобождавате след запазване:
procedure TFormMain.SaveActiveViewAsImage;
var
Pdf: TPdf;
Bmp: TBitmap;
Jpeg: TJpegImage;
begin
if not Assigned(FActivePdfView) or not FActivePdfView.Active then
Exit;
Pdf := FActivePdfView.Pdf;
Pdf.PageNumber := FActivePdfView.PageNumber;
Bmp := Pdf.RenderPage(
0, 0,
Round(Pdf.PageWidth * 2),
Round(Pdf.PageHeight * 2),
FActivePdfView.Rotation, [], clWhite);
try
if SavePictureDialog.Execute then
begin
Jpeg := TJpegImage.Create;
try
Jpeg.Assign(Bmp);
Jpeg.CompressionQuality := 90;
Jpeg.SaveToFile(SavePictureDialog.FileName);
finally
Jpeg.Free;
end;
end;
finally
Bmp.Free;
end;
end;
Умножителят 2x върху ширината и височината дава по-рязък (sharper) изход за документи с фин текст. try/finally около освобождаването на растерното изображение не е по избор; отмяната (cancel) на TSaveDialog все още удря (hits) блока finally и вие искате растерното изображение да бъде освободено, независимо какво е направил потребителят
Изисквания за DLL
PDFium Component обвива (wraps) родната (native) библиотека pdfium. 32-битов процес хост се нуждае от pdfium32.dll; 64-битов хост се нуждае от pdfium64.dll. Вариантите с V8 JavaScript двигателя добавят суфикса v8 и тежат приблизително 23-27 MB срещу 5-6 MB за стандартните компилации. За програма за преглед за сравнение, която деактивира попълването на формуляри (Pdf.FormFill := False), стандартната non-V8 компилация е достатъчна и поддържа дистрибуцията по-малка
Поставете DLL файла в същата директория като изпълнимия файл (executable) или във всяка директория от системния PATH. Компонентът го зарежда при поискване, когато първият TPdf е активиран, така че липсващ DLL излиза на повърхността (surfaces) в този момент, а не при стартиране на приложението. Ако доставяте инсталатор, най-надеждният подход е да копирате DLL файла в папката на приложението по време на инсталацията, вместо да разчитате на системна директория, която администратор може по-късно да почисти
V8 компилациите са полезни предимно когато трябва да взаимодействате с PDF JavaScript действия, например за задействане на полета за изчисление или манипулатори за изпращане (submit handlers). Пасивната програма за преглед за сравнение няма причина да изпълнява JavaScript; задаването на Pdf.FormFill := False преди Active := True напълно пропуска средата за попълване на формуляри, което също означава, че не се инициализира JS двигател, дори ако се използва стандартната компилация. Това е правилното решение по подразбиране за програма за преглед само за четене, независимо кой вариант на DLL доставяте
За допълнителни подробности относно PDFium Component и пълното му API, посетете продуктовата страница на Delphi PDFium Component