Teknisk artikel

PDF-annotationer i Delphi med HotPDF: Typer og rektangler

En annotation er ikke sideindhold. Når du kalder TextOut eller tegner et rektangel, bliver mærkerne en del af sidens indholdsstrøm, bagt ind i de bytes, som en renderer maler. En annotation er en separat ordbog, der hænger ud over siden gennem dens /Annots array, med sit eget rektangel, sit eget udseende og sin egen livscyklus. En læser kan åbne den, flytte den, skjule den eller fjerne den uden at røre en enkelt glyph af den underliggende side. Den adskillelse er hele grunden til, at annotationer eksisterer, og det er også kilden til de to ting, der overrasker folk først: hvor en annotation lander, og hvordan den ser ud, når en bestemt fremviser får fat i den

HotPDF eksponerer ISO 32000 annotationsundertillæg gennem en familie af AddXxxAnnotation-kald på sideobjektet. De deler alle den samme form: et rektangel, der fastgør annotationen på siden i PDF-brugerrum, en vis payload (tekst, et stempelnavn, et par punkter) og en farve. Få rektanglet rigtigt, og det meste af arbejdet er gjort. Resten er at vide, hvilke undertillæg der bærer deres eget udseende, og hvilke der læner sig op ad fremviseren for at tegne dem

A PDF page produced by HotPDF showing text note icons, free text boxes, square and line markups, and approval stamps placed across the page
Én side med flere annotationsundertillæg på én gang: tekstnoter, fri tekst, geometriske opmærkninger og stempler

Rektanglet er annotationen, ikke teksten

Hvert annotationskald tager et TRect, og det rektangel betyder noget andet end de koordinater, du sender til TextOut. For en tekstnote er det det klikbare hotspot, den lille region, hvor noteikonet sidder, og hvor et klik åbner kommentaren. For et firkantet eller frit tekstfelt er det det synlige omfang af opmærkningen. For et stempel er det den boks, som stempelkunsten skaleres til. Tallene er PDF-brugerrumspunkter, målt fra sidens nederste venstre hjørne med Y stigende opad, den samme konvention, som resten af HotPDF bruger

En tekstnote er den letteste undertype. Du giver den brødteksten, et rektangel til ikonet, et flag for, om den åbnes som standard, et ikonnavn og en farve

Pdf.CurrentPage.AddTextAnnotation(
  'Reviewer: confirm the totals on this line before sign-off.',
  Rect(120, 700, 140, 720),   // icon hotspot, ~20pt square
  False,                      // closed until the reader clicks it
  taComment,                  // bubble icon
  clBlue);

Rektanglet her er bevidst lille, omkring tyve punkter på en side, fordi en tekstnote kun er et ikon, indtil nogen klikker på det. Gør rektanglet stort, og du får ikke en stor note; du får et overdimensioneret klikmål, hvor ikonet er fastgjort til et hjørne. Open-flaget styrer, om pop-up'en vises, når dokumentet indlæses. Sæt en håndfuld noter True, og de stakkes oven på hinanden og oven på indholdet, så reserver det til den ene note, du rent faktisk ønsker, at læseren skal se med det samme

Ikonnavnet kommer fra THPDFTextAnnotationType, som knyttes til standardnote-ikonerne: taComment, taKey, taNote, taHelp, taParagraph, taNewParagraph og taInsert. Ikonet er det eneste, som typen ændrer. Det ændrer ikke adfærden, og det er værd at vide, at ikke alle fremvisere tegner alle syv; de sikre på tværs af gamle og nye læsere er taComment, taNote og taHelp

Fritstående tekst skrives på siden, men forbliver en annotation

En fri tekstannotation ligner indhold, fordi teksten er synlig uden et klik, og sidder i sit rektangel som en billedtekst. Det er stadig en annotation, med al den adskillelighed, det indebærer, hvilket er præcis, hvad du vil have for et anmeldelsesstempel eller en kladdeetiket, som nogen bør være i stand til at fjerne senere. Signaturen bytter ikonet og open-flaget ud med en justeringsværdi

Pdf.CurrentPage.AddFreeTextAnnotation(
  'DRAFT - not for distribution',
  Rect(200, 210, 400, 235),   // the box the text is laid into
  ftCenter,                   // ftLeftJust / ftCenter / ftRightJust
  clRed);

Her betyder rektanglet mere, end det gør for en tekstnote, fordi teksten ombrydes og justeres indeni. Gør boksen for kort, og teksten klippes ved den nederste kant; for smal, og den ombrydes steder, du ikke havde til hensigt. Justeringen kommer fra THPDFFreeTextAnnotationJust og har kun tre værdier. Fordi fritstående tekst er en opmærkningsannotation, kan en læser, der åbner filen i et redigeringsprogram, vælge den, flytte den eller slette den som en enhed, hvilket er forskellen, der afgør, om du rækker ud efter fritstående tekst eller bare tegner ordene med TextOut. Hvis etiketten skal være permanent, skal du tegne den. Hvis det er redaktionelt og meningen, at det skal af, skal du gøre det til en annotation

Geometriske og linje-opmærkninger til at pege på ting

Kvadrater, cirkler og linjer er de opmærkninger, du bruger til at pege på en region frem for at beskrive det med ord. AddCircleSquareAnnotation dækker de to boksformer gennem et THPDFCSAnnotationType med csCircle eller csSquare, hvor rektanglet angiver grænserne for formen

// A box drawn around a figure that needs attention
Pdf.CurrentPage.AddCircleSquareAnnotation(
  'Check this region against the source data',
  Rect(50, 300, 120, 360),
  csSquare,
  clGreen);

// A line, given two points rather than a rectangle
var
  StartPt, EndPt: THPDFCurrPoint;
begin
  StartPt.X := 130; StartPt.Y := 360;
  EndPt.X   := 250; EndPt.Y   := 320;
  Pdf.CurrentPage.AddLineAnnotation(
    'Points from the note to the figure',
    StartPt, EndPt,
    clBlue);
end;

Bemærk, at linje-annotationen bryder med rektangelmønsteret: den modtager to THPDFCurrPoint records, en start og en slutning, fordi en linje er defineret ved dens endepunkter og ikke en omgivende boks. Farven indstiller stregen. Hvis du ønsker pilehoveder, har HotPDF overstyringer af AddLineAnnotation, der accepterer linjeafslutningsstile, men den simple form med tre argumenter tegner bare en linje, som som regel er det et callout har brug for

Tekstopmærkningsundertyper fungerer på en region, du allerede har lagt ud. AddHighlightAnnotation tager et rektangel, valgfrit indhold og en farve, der som standard er gul, og toner området på samme måde som en overstregningstusch. Den er beregnet til at ligge hen over rigtig tekst, så rektanglet bør matche grænserne for de ord, du tegnede, hvilket betyder, at du generelt beregner den ud fra de samme koordinater, du har givet til TextOut i stedet for at gætte

Stempler afhænger af fremviseren til at tegne dem

En stempel-annotation er den, der med størst sandsynlighed ser anderledes ud fra én læser til den næste, og grunden til dette er værd at forstå. AddStampAnnotation navngiver et standardstempel via THPDFStampAnnotationType, med værdier såsom satApproved, satConfidential, satFinal, satDraft og satForComment

Pdf.CurrentPage.AddStampAnnotation(
  'Approved for release on review',
  Rect(50, 400, 200, 440),
  satApproved,
  clGreen);

Stempelnavnet er en anmodning. PDF definerer sættet af standardstempelnavne, men ikke illustrationen bag dem, så hver fremviser leverer sin egen gengivelse af "APPROVED" eller "CONFIDENTIAL", og nogle få tegner slet ingenting for de navne, de ikke genkender. Rektanglet bestemmer den boks, som illustrationen skaleres til, og farven er et tip, fremviseren kan ære, eller vælge ikke at ære. Hvis et stempel skal se ens ud overalt, er den pålidelige vej slet ikke et standardstempel: tegn selve markeringen med TextOut og de tegnende kald, eller placér det som en fri tekstannotation, hvis udseende du styrer. Grib fat i standardstemplet, når du vil have fremviserens velkendte udseende og kan tolerere at der er en vis variation

Filvedhæftninger følger det samme form som rectangle-plus-payload. AddFileAttachmentAnnotation modtager beskrivelsen, stien til den fil, der skal indlejres, et rektangel til papirclipsikonet og en farve. Filen kører med indeni i PDF'en, og ikonet er det håndtag, en læser bruger til at udtrække den med

Hvordan annotationer adskiller sig fra AcroForm-felter

Den forvirring, der koster mest tid, er, når man forsøger at behandle en annotation, som var den et formularfelt. Begge hæftes til siden via /Annots, og et formularfelt er reelt set en speciel annotationsundertype (en widget), og det er derfor, de ser ud til at være beslægtede. Men de er ikke udskiftelige. Et formularfelt har en værdi, det har et navn, indgår i en fanerækkefølge og kan blive indsendt, nulstillet, eller have scripts tilknyttet; du skaber disse vha. AddTextField, AddCheckBox og AddPushButton kald og altså ikke vha. annotationskaldene på denne side. En markup-annotation (opmærknings-annotation) indeholder en kommentar eller en form, og har ingen værdi der skal indsendes, og det er netop det forkerte værktøj, i det øjeblik du har brug for at indsamle et input

Den praktiske test er ganske simpel. Hvis en bruger forventes at skrive noget, at vælge noget eller at klikke på noget, som dokumentet skal huske - så har du brug for et AcroForm felt. Hvis du vil efterlade en note, opmærke en region, eller stemple en form for status, som skal rejse afsted med filen, men som dog ikke er data i sig selv - så er det en annotation du har brug for. En sammenblanding af de 2 ting forårsager blot at du danner nogle dokumenter, der ser ud til at være rigtige, men som opfører sig forkert: du får fx et "felt" som ingen kan udfylde, eller en kommentar, der forsvinder, det øjeblik når formularen nulstilles. Selve den interaktive del, der foregår med felttyper, validering, og de særlige handlinger for "Submit" (indsending), er sit helt eget emneområde som er dækket i Gennemgang af AcroForm-felter og handlingsmuligheder

Sammensætning af en side

Stykkerne komponerer på samme måde, som resten af HotPDF gør det. Man indstiller dokumentets egenskaber, fremkalder BeginDoc, indtegner alt det sideindhold, man har brug for med tekst- og grafik-kald, lægger til sidst de ønskede annotationer oven på det hele, og lukker ned med EndDoc. Annotationer tilkobler sig CurrentPage, så umiddelbart efter en AddPage vil de lande på den nye side, og en note som du egentlig havde tiltænkt at side et skulle bære, vil roligt og uforstyrret dukke frem på side to, ifald du tillægger den der efter sideskiftet

Pdf := THotPDF.Create(nil);
try
  Pdf.FileName := 'annotated.pdf';
  Pdf.Compression := cmFlateDecode;
  Pdf.FontEmbedding := True;
  Pdf.BeginDoc;

  Pdf.CurrentPage.SetFont('Arial', [], 11);
  Pdf.CurrentPage.TextOut(50, 740, 0, 'Quarterly figures, draft for review');

  Pdf.CurrentPage.AddTextAnnotation(
    'Confirm the totals before sign-off.',
    Rect(50, 720, 70, 740), False, taComment, clBlue);
  Pdf.CurrentPage.AddFreeTextAnnotation(
    'DRAFT', Rect(450, 720, 540, 745), ftCenter, clRed);
  Pdf.CurrentPage.AddStampAnnotation(
    'For comment', Rect(50, 660, 180, 695), satForComment, clGreen);

  Pdf.EndDoc;
finally
  Pdf.Free;
end;

Et sidste godt instinkt at udvikle, når outputtet ser forkert ud: åbn filen i mere end én fremviser, før du slår fast, at koden er i stykker. Stempler og de sjældnere note-ikoner er de sædvanlige syndere, og fordi annotationen er en anmodning til læseren snarere end de er malede pixels, så er en forskel mellem Acrobat og en mere letvægts-fremviser oftest bare et bevis for at specifikationen fungerer fuldstændigt som den er designet til, frem for en fejl, der er sket, under dit kald

Annotations-kaldene som vises her er en del af HotPDF-komponenten til Delphi og C++Builder