Правоъгълник, начертан около абзац по време на преглед, не е нужно да се превръща в маркировка вътре в PDF. THPDFViewerModel на HotPDF предоставя AddHighlightRegion, метод, който пази всяко маркиране като запис в паметта, а не като промяна на заредения документ, така че проверяващият може да маркира десетки страници, докато файлът на диска остава байт по байт същият. Увеличете до 6400%, завъртете страницата на 90 градуса, преминете от Fit Width към Fit Page и същият правоъгълник пак ще попадне върху същия абзац, защото математиката на координатите използва действителната геометрия на рендерирането в момента, когато маркировката е начертана
Инструментите за преглед, изградени около PDF четец, постоянно се сблъскват с този проблем. Екран за редакционни бележки, проверка на генерирани фактури, вътрешен процес за одобрение: всички те трябва да позволят на някого да привлече вниманието към област от страница, без всяка чернова да се превръща в трайна промяна на файла и без да се въвежда цялостна подсистема за анотации само за показване на цветна рамка, докато все още се решава дали маркировката трябва да остане. HotPDF отговаря с отделен слой за маркиране, който изцяло стои от страната на Model в разделението, описано в изграждането на собствен PDF четец с MVC архитектура в Delphi, поради което същият списък с маркировки може да се управлява и от модулен тест без никакъв прозоречен манипулатор
Какво действително съхранява AddHighlightRegion
AddHighlightRegion съхранява точно три неща за всяка маркировка: базиран от нула индекс на страницата, THPDFRectangle в координати на потребителското пространство на PDF и TColor, всички пакетирани като запис THPDFViewerHighlight вътре в THPDFViewerModel. Извикването на Viewer.HighlightRegion(PageIndex, PageRect, clYellow) или еквивалентното Model.AddHighlightRegion добавя един от тези записи към частен масив и връща неговия индекс, а този индекс е единственият дескриптор, който извикващият получава: няма отделен обект, няма интерфейс с броене на препратки и няма какво да се освобождава. Всяка друга възможност в тази статия — рисуване на маркировката, пренасочване след промяна на мащаба и изтриване — е изградена върху този един малък запис
Всеки правоъгълник се нормализира и изрязва, преди да бъде приет. AddHighlightRegion разменя левия и десния ръб, ако проверяващият плъзне отдясно наляво, разменя горния и долния при плъзгане нагоре, след което изрязва резултата спрямо MediaBox на страницата, получен чрез GetLoadedPageBox. Правоъгълник, който завършва с нулева ширина, нулева височина или изцяло извън страницата, се отхвърля директно: методът връща -1 и към списъка не се добавя нищо. Тази върната стойност не е декоративна: група маркировки, възстановена от външен файл за преглед или от остарели координати след замяна на страница, може тихо да загуби записи, ако извикващият не я провери
Как маркировката остава подравнена след мащабиране или завъртане
Маркировката остава подравнена, защото HotPDF я съхранява в пространството на PDF страницата и я проектира отново в пространството на екрана при всяко прерисуване, вместо да пази правоъгълник на екрана, който би остарял веднага щом се промени мащабът. THPDFViewerModel.PagePointToView и обратният му метод ViewPointToPage извършват тази проекция на два етапа: първо собственото поле /Rotate на страницата, след това независимото ViewRotation на Viewer, което никога не се записва обратно в PDF и влияе само на това, което Viewer показва. Отмяната на преобразуването при отпускане на мишката изпълнява същите два етапа в обратен ред, което позволява маркировка, начертана при голямо увеличение върху страница, завъртяна на 270 градуса, да попадне точно на правилното място, след като проверяващият върне изгледа към Fit Page
DPI, използвано за тази проекция, е толкова важно, колкото и завъртането. Viewer на HotPDF прихваща точното DPI на растерното изображение, което в момента се вижда на екрана, в FRenderedDPI веднага след всяко рендериране, а ImageMouseUp подава същата стойност към ViewPointToPage, така че координатата на мишката винаги се преобразува с реалната резолюция, при която е била начертана, а не с резолюция, преизчислена от текущото свойство за мащаб. CreatePageSnapshot и сродните му методи ограничават DPI в диапазона от 12 до 2400, но интерактивният път на рендериране няма такъв таван: стандартната стълбица за мащабиране достига 6400%, което при базова стойност 96 DPI изчислява значително над 2400 DPI, така че повторното използване на ограничение като при моментна снимка за картографиране на координатите би изместило всяка маркировка с няколко пиксела в горната част на диапазона. Два по-малки параметъра по подразбиране допълват взаимодействието: плъзгане, по-кратко от два пиксела по която и да е ос, се приема за клик и не създава маркировка, а маркирането не може да започне, докато поне една страница не е била рендерирана, тъй като FRenderedDPI започва от нула
Свързване на интерактивното маркиране с екран за преглед
Включването на интерактивното маркиране е задача от три свойства върху самия контрол THPDFViewer: задайте InteractionMode на vimHighlight вместо на стандартното vimBrowse, изберете HighlightColor, което по подразбиране е clYellow, и обработете OnMarqueeSelect, за да разберете какво току-що е начертал проверяващият. Всичко останало — прихващането на мишката, рисуването на пунктиран правоъгълник за избор по време на плъзгане, преобразуването на точката на отпускане обратно към пространството на страницата и извикването на AddHighlightRegion — се случва вътре в контрола, преди да бъде задействано това събитие
type
TReviewForm = class(TForm)
Viewer: THPDFViewer;
ReviewLog: TMemo;
procedure FormCreate(Sender: TObject);
private
procedure ViewerMarqueeSelect(Sender: TObject; Shift: TShiftState;
PageIndex: Integer; const PageRect: THPDFRectangle;
HighlightIndex: Integer);
end;
// PdfDoc is a THotPDF already loaded elsewhere on the form
procedure TReviewForm.FormCreate(Sender: TObject);
begin
Viewer.PDFDocument := PdfDoc;
Viewer.InteractionMode := vimHighlight;
Viewer.HighlightColor := clLime;
Viewer.OnMarqueeSelect := ViewerMarqueeSelect;
end;
procedure TReviewForm.ViewerMarqueeSelect(Sender: TObject; Shift: TShiftState;
PageIndex: Integer; const PageRect: THPDFRectangle; HighlightIndex: Integer);
begin
ReviewLog.Lines.Add(Format('page %d, mark #%d at (%.1f, %.1f)-(%.1f, %.1f)',
[PageIndex + 1, HighlightIndex, PageRect.Left, PageRect.Bottom,
PageRect.Right, PageRect.Top]));
end;
OnMarqueeSelect се задейства само за плъзгане, което действително е създало маркировка: клик, твърде малък, за да се счита за плъзгане, незабавно изчиства слоя за избор, а плъзгане, което завършва изцяло извън страницата, достига до AddHighlightRegion, но се отхвърля там по същия начин като програмно извикване, така че събитието остава тихо и в двата случая. Има една подробност от реализацията, която е полезно да се знае, ако маркирането някога спре да реагира по краищата на контрола: прихващането на мишката принадлежи на самия THPDFViewer, наследник на TScrollBox, а не на вътрешния TImage, който показва растерното изображение на страницата, което позволява на проверяващия да плъзне отвъд ръба на рендерираната страница и пак да получи чисто отпускане
Добавяне, премахване и повторно прочитане на маркировки от код
Маркировките изобщо не е нужно да идват от плъзгане с мишката. Viewer.HighlightRegion(PageIndex, PageRect, Color), което преминава към същото Model.AddHighlightRegion, извиквано вътрешно от интерактивното плъзгане, е публично именно за да може екранът за преглед да възстанови маркировки от данни, с които вече разполага: коментари, заредени от база данни, резултати от текстово търсене или маркировки, възстановени от предишна сесия. Тъй като координатите са обикновени числа в потребителското пространство на PDF, нищо в този път не зависи от това страницата да е била рендерирана предварително, за разлика от интерактивното плъзгане, което изисква FRenderedDPI вече да съдържа реална стойност
var
I: Integer;
Item: TPriorComment; // your own record: PageIndex + PageRect
NewIndex: Integer;
begin
for I := 0 to PriorComments.Count - 1 do
begin
Item := TPriorComment(PriorComments[I]);
NewIndex := Viewer.HighlightRegion(Item.PageIndex, Item.PageRect, clAqua);
if NewIndex < 0 then
LogWarning('comment %d fell outside the page and was dropped', [I]);
end;
end;
Премахването на една маркировка показва как работи съхранението, базирано на масив. RemoveHighlightRegion изтрива един запис и измества всеки следващ запис с една позиция назад, за да затвори празнината, което означава, че всеки индекс, запазен по-рано — от събитие OnMarqueeSelect или от предишно изброяване — вече не е надежден, щом бъде премахнат елемент преди него в списъка. OnHighlightChange се задейства при всяко добавяне, премахване и извикване на ClearHighlightRegions, но не носи информация какво се е променило, затова безопасният модел е да го приемате като сигнал да изградите отново всеки списък, показван от панела за преглед, чрез HighlightCount и TryGetHighlightRegion, вместо да поправяте кеширан индекс на място
procedure TReviewForm.ViewerHighlightChange(Sender: TObject);
var
I: Integer;
Mark: THPDFViewerHighlight;
begin
MarkList.Items.Clear;
for I := 0 to Viewer.Model.HighlightCount - 1 do
if Viewer.Model.TryGetHighlightRegion(I, Mark) then
MarkList.Items.AddObject(Format('page %d', [Mark.PageIndex + 1]),
TObject(I));
end;
Кога маркировката трябва да стане истинска анотация Highlight
Областта за маркиране трябва да стане истинска анотация в момента, когато трябва да преживее конкретния екземпляр на THPDFViewer. HotPDF също предоставя AddHighlightAnnotation за нова страница и AddLoadedHighlightAnnotation за вече зареден документ, а въпреки почти еднаквото име това е напълно различен механизъм: и двете записват действителна анотация за текстова маркировка ISO 32000-1 §12.5.6.10, PDF /Subtype /Highlight, в масива /Annots на страницата, с /QuadPoints, които отбелязват точния ред от глифове, и всеки съвместим PDF четец я рендерира, щом файлът бъде записан, а не само собственият четец на HotPDF. Същата граница между механизмите определя и дали маркировката ще премине през XFDF: анотация, създадена с AddLoadedHighlightAnnotation, е нормален PDF обект, който ExportLoadedAnnotationsToXFDF прихваща и предава на Acrobat или друг инструмент за преглед като маркировка ISO 19444-1, разгледана в импортирането и експортирането на PDF анотации като XFDF в Delphi, докато област, добавена чрез AddHighlightRegion, е невидима за този експорт, защото никога не е записана в графа на обектите: тя съществува само докато съществува THPDFViewerModel, който я е създал. Пълното семейство от маркировки и геометрични типове анотации, налични на страница, както и начинът, по който правоъгълникът позиционира всеки от тях, е разгледано в статията за PDF анотациите в Delphi с HotPDF, а практическото правило е просто: пазете маркировката временна, докато документът още се обсъжда, и я запишете като анотация, щом решението стане окончателно
Къде свършва слоят за маркиране
От своя страна слоят за маркиране не се опитва да изглежда като полупрозрачен маркер: RefreshDocument рисува всяка област като правоъгълник с контур от два пиксела в нейния собствен цвят върху кешираното растерно изображение на страницата, по същия начин, по който рисува попаденията от търсене, вместо да смесва цветно запълване върху текста отдолу, така че класическият вид на жълто прозрачно поле трябва да бъде нарисуван в кода на приложението или отложен до собственото поле за изглед на повишена анотация. Една възможност, която си струва да се използва след създаването на област, е CreateCurrentPageRegionSnapshot, което приема същия THPDFRectangle, носен от маркировката, и рендерира само тази област в растерно изображение, полезно за прикачване на малка предварителна картина към коментар от преглед, без да се експортира цялата страница. Една версия за преглед не трябва да избира предварително между двата механизма: задайте всяка нова маркировка по подразбиране като временна област THPDFViewerHighlight, докато нишката с коментари остава отворена, и извикайте AddLoadedHighlightAnnotation едва след като проверяващият я разреши, което оставя заредения PDF непокътнат по време на обсъждането, създаващо най-много промени. Описаният тук контрол за преглед е част от стандартния HotPDF Component за Delphi и C++Builder, наред с останалите API за анотации и формуляри, посочени по-горе