PDF хипервръзките са URI анотации: правоъгълник, покриващ някаква област от страницата, който при щракване казва на програмата за преглед да отвори URL. Анотацията и текстът под нея са напълно независими обекти. PrintHyperlink на HotPDF обединява и двете в едно извикване, чертаейки текста и изчислявайки правоъгълника на анотацията от метриките на рендирания текст. Това удобство скрива детайл, който си струва да разберете, преди да пишете код за продукция. Освен това то не е цялата история: AddURILink поставя област за щракване върху съдържание, което сте начертали сами, а AddGoToLink се справя с вътрешната навигация — и двете разгледани по-долу
Как работи PrintHyperlink
PrintHyperlink живее в THPDFPage и приема четири аргумента: X и Y координати (в пунктове, начало долу-вляво, Y се увеличава нагоре), етикетния низ (label string) за чертаене и URL целта. Вътрешно той извиква TextOut в текущия цвят на хипервръзката, след което веднага изчислява правоъгълника на анотацията от TextWidth и TextHeight при текущите метрики на шрифта. Това означава, че шрифтът и размерът трябва да бъдат зададени преди извикването и не трябва да се променят между чертаенето на етикета и поставянето на анотацията, защото и двете се решават в едно и също извикване
Цветът по подразбиране е clBlue. SetRGBHyperlinkColor го променя само за последващи извиквания; той не актуализира със задна дата (retroactively) вече написани анотации. Ако се нуждаете от различни цветове за различни групи връзки на една и съща страница, извикайте SetRGBHyperlinkColor преди всяка група и го нулирайте след това
Ето минимален документ, който записва три връзки с два различни цвята:
procedure CreateLinkedReport(const FileName: string);
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := FileName;
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 11);
// Default blue for informational links
Pdf.CurrentPage.TextOut(50, 750, 0, 'Reference links:');
Pdf.CurrentPage.PrintHyperlink(50, 720, 'Product page', 'https://www.loslab.com/en-us/pdf-library/delphi-pdf-component.html');
Pdf.CurrentPage.PrintHyperlink(50, 695, 'Online manual', 'https://www.loslab.com/en-us/pdf-library/delphi-pdf-component.html');
// Red for the action link
Pdf.CurrentPage.SetRGBHyperlinkColor(clRed);
Pdf.CurrentPage.PrintHyperlink(50, 660, 'Purchase license', 'https://www.loslab.com/en-us/buy-hotpdf-fastspring.html');
Pdf.CurrentPage.SetRGBHyperlinkColor(clBlue); // restore default
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Координатният капан
HotPDF използва начало долу-вляво с нарастващо нагоре Y, в пунктове (1/72 инча). Страница A4 е 595 x 842 pt; страница US Letter е 612 x 792 pt. Y=750 седи близо до върха на страница A4, а Y=50 би било близо до долното поле (margin). Всеки, идващ от екранна графика или HTML, предполага обратното и поставя първия ред на връзката направо извън видимата област
Правоъгълникът на анотацията, който PrintHyperlink изчислява, използва същата координатна система. Ако по-късно завъртите страницата, мащабирате я или промените размера на страницата, без да преизчислите вашите X/Y стойности, видимият текст и правоъгълникът за щракване ще се разминат (drift apart). Връзката "работи" в смисъл, че щракването някъде близо до текста задейства URL адреса, но горещата зона (hot zone) вече не съвпада с това, което читателят вижда. Тествайте на действителния размер на страницата и нивото на увеличение, което доставяте, а не само на машината за разработка при 100%
Един случай, при който разминаването е гарантирано: ако извикате PrintHyperlink с координати, подходящи за страница A4, и след това превключите към потребителска страница с тесен формат, без да коригирате стойностите на X/Y, анотацията може да се окаже извън страницата изцяло. Обектът на анотацията все още е записан в PDF; повечето програми за преглед го изрязват (clip) безшумно, така че връзката просто изчезва без никаква грешка
Текст на етикета срещу URL цел
Аргументите Text и Link са независими. Можете да начертаете "Изтегляне на фактура PDF", докато целта е напълно квалифициран HTTPS URL с параметри на заявката. Това разделение е умишлено; видимият етикет трябва да може да се чете от човек, а URL адресът може да бъде дълъг или генериран динамично
Това, което създава проблеми, е когато етикетът е самият суров URL, особено дълъг такъв. Ако URL адресът се пренася визуално на два реда, но правоъгълникът на анотацията е бил изчислен за едноредов низ, само първият ред ще може да се щракне. PrintHyperlink не се справя с многоредов поток (multi-line flow); поддържайте етикета достатъчно къс, за да се побере на един ред при текущия размер на шрифта и ширината на страницата, използвайте къс описателен етикет с пълния URL адрес като цел или приложете заобиколното решение (workaround) ред по ред, показано в следващия раздел
За документи, които ще бъдат архивирани или разпространявани без активна интернет връзка, също помислете дали самият URL трябва да се появява в отпечатана форма някъде в тялото на документа, а не само като метаданни на анотация. Читател, който отпечатва PDF на хартия, не получава нищо от URI анотация
Заобикаляне на многоредовото ограничение
Когато етикет на връзка наистина трябва да обхваща повече от един ред — дълъг URL, отпечатан дословно (verbatim), или пренесено изречение, което трябва да може да се щракне от край до край — решението е да спрете да го третирате като една връзка и да го третирате като една връзка на ред. Всяко извикване на PrintHyperlink изчислява своя правоъгълник от текста, който чертае, така че няколко извиквания, споделящи същата цел на Link, произвеждат няколко правилно оразмерени анотации, които всички отварят един и същ URL. Читателят не може да направи разликата; всеки ред реагира на щракване
procedure PrintWrappedHyperlink(Page: THPDFPage; X, TopY, LineStep: Single;
const Lines: array of AnsiString; const Link: AnsiString);
var
I: Integer;
begin
for I := 0 to High(Lines) do
Page.PrintHyperlink(X, TopY - I * LineStep, Lines[I], Link);
end;
// Usage: break the label at the positions where your layout wraps it
Pdf.CurrentPage.SetFont('Arial', [], 10);
PrintWrappedHyperlink(Pdf.CurrentPage, 50, 400, 14,
['https://www.loslab.com/en-us/pdf-library/',
'delphi-pdf-component.html'],
'https://www.loslab.com/en-us/pdf-library/delphi-pdf-component.html');
Разделянето на низа е ваша отговорност: прекъснете го на същите позиции, където би се пренесъл визуално при текущия шрифт и ширина на колоната, като използвате TextWidth за тестване на всеки кандидат-ред. Алтернативата е сами да начертаете пренесения текст с обикновени извиквания на TextOut и след това да поставите по един правоъгълник AddURILink върху всеки ред — по-добрият път, когато текстът вече е произведен от вашата собствена логика за пренасяне на думи, което ни води до тази функция
AddURILink: области за щракване върху всичко, което сте начертали
PrintHyperlink е обвивка за удобство (convenience wrapper): тя чертае свой собствен етикет и извлича правоъгълника от метриките на този етикет. AddURILink е половината от по-ниско ниво, изложена директно:
function AddURILink(Rectangle: TRect; const URL: AnsiString;
const Description: AnsiString = ''): THPDFDictionaryObject;
Тя записва само анотацията — не се чертае текст и не се променя цвят. Rectangle се интерпретира в същото координатно пространство като вашите извиквания за чертаене, така че можете да използвате повторно точните стойности на X/Y, които сте подали на TextOut или извикване на изображение. Това я прави правилния инструмент винаги, когато видимото съдържание вече съществува: гореща точка на изображение (image hotspot), клетка от таблица, блок текст, начертан по-рано, или един ред от пренесен абзац, както в заобикалянето по-горе. Анотацията носи граница с нулева ширина (zero-width border), така че нищо видимо не се променя; областта за щракване е точно правоъгълникът, който сте посочили
Функцията връща речника на анотацията като THPDFDictionaryObject. Повечето извикващи (callers) отхвърлят резултата, но запазването му ви позволява да коригирате записите на анотацията, преди документът да бъде записан
Два детайла за съответствие (compliance) са вградени. В режими PDF/A флагът за печат на анотацията е зададен, както изискват тези стандарти. При PDFUACompliance параметърът Description трябва да бъде непразен низ — той става запис /Contents на анотацията, което е това, което помощните технологии обявяват за връзката — и извикването повдига изключение, вместо безшумно да излъчи несъответстващ файл (non-conforming file). PrintHyperlink предхожда това правило и не прикрепя описание, така че за PDF/UA изход начертайте етикета с TextOut и поставете анотацията с AddURILink плюс смислено описание
Правилото за решение е просто: използвайте PrintHyperlink, когато връзката е кратък текст, който все още не сте начертали; използвайте AddURILink, когато областта за щракване се дефинира от съдържание, което вие самите чертаете или измервате
Вътрешна навигация с AddGoToLink
Външните URL адреси са само половината от това, което правят анотациите за връзки. Другата половина е навигацията вътре в документа — съдържание (TOC), което скача до глави, кръстосани препратки между секции. HotPDF излага това чрез AddGoToLink:
procedure AddGoToLink(Rectangle: TRect; TargetPageIndex: Integer;
YPos: Single = -1; const Description: AnsiString = '');
Три семантики си струва да бъдат посочени точно, тъй като никоя не може да се отгатне от сигнатурата. TargetPageIndex е базиран на нула: първата страница на документа е страница 0, съответстваща на CurrentPageNumber. Целевата страница трябва вече да съществува, когато правите извикването; ако индексът е извън обхват, процедурата се връща (returns), без да добави анотация — няма изключение, няма връзка, няма предупреждение. За съдържание, което сочи напред, първо създайте всички страници, след това се върнете обратно и добавете връзките
YPos избира вертикалната позиция на целевата страница, в същото координатно пространство като вашите извиквания за чертаене. Стойността по подразбиране от -1 (всяка отрицателна стойност) записва нулева координата на дестинацията (null destination coordinate), казвайки на програмата за преглед да запази текущата си вертикална позиция, когато кацне на целевата страница. Подайте неотрицателна стойност и програмата за преглед превърта (scrolls), така че тази позиция да седи в горната част на прозореца — използвайте Y координатата на заглавието, към което сочи връзката. Увеличението (Zoom) винаги се оставя непроменено. Както при AddURILink, Description трябва да е непразно при PDFUACompliance и става алтернативен текст на връзката
procedure BuildLinkedTOC(const FileName: string);
const
Chapters: array[0..2] of string =
('Introduction', 'Installation', 'API Reference');
var
Pdf: THotPDF;
I, Y: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := FileName;
Pdf.BeginDoc; // page 0 becomes the TOC page
// Create the chapter pages first so the link targets exist
for I := 0 to High(Chapters) do
begin
Pdf.AddPage; // pages 1..3
Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
Pdf.CurrentPage.TextOut(50, 780, 0, Chapters[I]);
end;
// Switch back to page 0 and draw the TOC entries with their links
Pdf.CurrentPageNumber := 0;
Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Contents');
Pdf.CurrentPage.SetFont('Arial', [], 11);
Y := 720;
for I := 0 to High(Chapters) do
begin
Pdf.CurrentPage.TextOut(70, Y, 0, Chapters[I]);
Pdf.CurrentPage.AddGoToLink(
Rect(70, Y + 14, 300, Y - 3), // covers the entry with padding
I + 1, // zero-based: chapters are pages 1..3
780, // land with the heading at the top
AnsiString('Go to ' + Chapters[I]));
Y := Y - 25;
end;
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Всеки запис получава правоъгълник, по-широк от текста, така че целият ред да реагира на показалеца, и всяка връзка каца със заглавието на главата (начертано на Y=780) в горната част на прозореца. Ако по-късно вмъкнете страница преди главите, всеки TargetPageIndex се измества с един; изчислявайте индексите от вашия цикъл за създаване на страници, вместо да ги кодирате твърдо (hard-coding)
Пълен пример за генериране на документ
Моделът по-долу показва по-реалистичен сценарий: генериране на кратък отчет със секция на хедър, основен текст и долен ред от връзки в долния колонтитул, всички от код, а не от форма с полета TEdit:
procedure GenerateProductSheet(
const FileName, ProductName, ProductURL, SupportURL: string);
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := FileName;
Pdf.Compression := cmFlateDecode;
Pdf.BeginDoc;
// Header
Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
Pdf.CurrentPage.TextOut(50, 750, 0, WideString(ProductName));
// Body paragraph placeholder
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 710, 0, 'See the links below for full documentation.');
// Footer links
Pdf.CurrentPage.SetFont('Arial', [], 10);
Pdf.CurrentPage.TextOut(50, 80, 0, 'Links:');
Pdf.CurrentPage.PrintHyperlink(50, 60, 'Product page', ProductURL);
Pdf.CurrentPage.PrintHyperlink(200, 60, 'Support', SupportURL);
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Обърнете внимание, че SetFont се извиква преди всяка група извиквания за текст. Шрифтът не се запазва през AddPage и ако забравите да го зададете преди PrintHyperlink на нова страница, правоъгълникът на анотацията ще бъде изчислен спрямо това, каквито и да са метриките по подразбиране на страницата, които може да се различават от това, което очаквате
Къде обработката на анотации варира в различните програми за преглед
PDF URI анотациите са дефинирани в ISO 32000-1 §12.6.4.7 и всяка съответстваща (conforming) програма за преглед трябва да ги следва. На практика няколко поведения се различават според програмата за преглед. Adobe Acrobat показва подкана за сигурност при първо щракване за URL адреси, които не са в списъка с доверени домейни; много браузъри и леки четци не го правят. Някои enterprise програми за преглед на PDF в силно защитени среди напълно деактивират URI анотациите чрез политика, така че щракването не прави нищо, без видима грешка. Мобилните PDF приложения варират по отношение на това дали отварят връзки вътре в уеб изгледа на приложението или предават към системния браузър
Никое от тези не е бъг, който можете да поправите от страната на генерирането; това са решения на политиката на програмата за преглед. Това, което можете да направите, е да пишете етикети на връзки, които правят URL адреса видим и в тялото на документа, така че читател в ограничена среда все още да може да копира адреса ръчно. Анотацията е удобството; текстът е резервният вариант (fallback)
Още един детайл, който си струва да знаете: PDF URI анотациите не носят никакво визуално подчертаване по подразбиране. Подчертаването, което виждате в повечето програми за преглед, се чертае от самата програма за преглед въз основа на типа анотация, а не от глиф в потока от съдържание. Ако имате нужда от физическо подчертаване, което оцелява при отпечатване на неинтерактивен рендерер или преобразуване от PDF към изображение, начертайте го изрично с LineTo и Stroke на подходящото Y отместване под базовата линия на текста. Това е отделна операция за чертаене, не нещо, с което PrintHyperlink се справя вместо вас
API за хипервръзки, показано тук, е част от компонента HotPDF за Delphi и C++Builder