Technisch artikel

PDF-annotaties in Delphi met HotPDF: Typen en Rects

Een annotatie is geen pagina-inhoud. Wanneer u TextOut aanroept of een rechthoek tekent, worden de markeringen onderdeel van de content stream van de pagina, ingebakken in de bytes die een renderer schildert. Een annotatie is een afzonderlijk woordenboek (dictionary) dat aan de pagina hangt via zijn /Annots array, met zijn eigen rechthoek, zijn eigen uiterlijk en zijn eigen levenscyclus. Een lezer kan het openen, verplaatsen, verbergen of verwijderen zonder ook maar één glyph van de onderliggende pagina aan te raken. Die scheiding is de hele reden waarom annotaties bestaan, en het is ook de bron van de twee dingen die mensen als eerste verrassen: waar een annotatie terechtkomt, en hoe deze eruitziet zodra een bepaalde viewer deze in handen krijgt

HotPDF stelt de ISO 32000 annotatiesubtypen beschikbaar via een familie van AddXxxAnnotation-aanroepen op het paginaobject. Ze delen allemaal dezelfde vorm: een rechthoek die de annotatie op de pagina fixeert in PDF user space (gebruikersruimte), enige payload (tekst, een stempelnaam, een paar punten) en een kleur. Krijg de rechthoek goed en het meeste werk is gedaan. De rest is weten welke subtypen hun eigen uiterlijk met zich meedragen en welke erop vertrouwen dat de viewer ze tekent

Een PDF-pagina geproduceerd door HotPDF met tekstnotitiepictogrammen, vrije tekstvakken, vierkante en lijnmarkeringen, en goedkeuringsstempels geplaatst over de hele pagina
Eén pagina met verschillende annotatiesubtypen tegelijk: tekstnotities, vrije tekst, geometrische markeringen en stempels

De rechthoek is de annotatie, niet de tekst

Elke annotatie-aanroep neemt een TRect aan, en die rechthoek betekent iets anders dan de coördinaten die u aan TextOut doorgeeft. Voor een tekstnotitie is het de klikbare hotspot, de kleine regio waar het notitiepictogram zich bevindt en waar een klik de opmerking opent. Voor een vierkant of een vrij tekstvak (free text box) is het de zichtbare omvang van de markering. Voor een stempel is het het vak waarin de stempelkunst wordt geschaald. De getallen zijn PDF user-space punten, gemeten vanaf de linkerbenedenhoek van de pagina waarbij Y naar boven toe toeneemt, dezelfde conventie die de rest van HotPDF gebruikt

Een tekstnotitie is het lichtste subtype. U geeft het de hoofdtekst, een rechthoek voor het pictogram, een vlag of het standaard wordt geopend, een pictogramnaam en een kleur

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);

De rechthoek is hier opzettelijk klein, ongeveer twintig punten aan een zijde, omdat een tekstnotitie slechts een pictogram is totdat iemand erop klikt. Maakt u de rechthoek groot, dan krijgt u geen grote notitie; u krijgt een extra groot klikdoel met het pictogram in één hoek vastgepind. De Open vlag bepaalt of de pop-up zichtbaar is wanneer het document laadt. Zet een handvol notities op True en ze stapelen zich bovenop elkaar en bovenop de inhoud, dus bewaar dat voor die ene notitie die u de lezer daadwerkelijk direct wilt laten zien

De pictogramnaam komt van THPDFTextAnnotationType, wat overeenkomt met de standaard notitiepictogrammen: taComment, taKey, taNote, taHelp, taParagraph, taNewParagraph en taInsert. Het pictogram is het enige wat het type verandert. Het verandert het gedrag niet, en het is de moeite waard te weten dat niet elke viewer ze alle zeven tekent; de veilige keuzes over oude en nieuwe lezers heen zijn taComment, taNote en taHelp

Vrije tekst schrijft op de pagina, maar blijft een annotatie

Een annotatie met vrije tekst (free text) ziet eruit als inhoud omdat de tekst zichtbaar is zonder te klikken, zittend in zijn rechthoek als een bijschrift. Het is nog steeds een annotatie, met alle scheidbaarheid van dien, wat precies is wat u wilt voor een beoordelingsstempel (review stamp) of een conceptlabel dat iemand later moet kunnen verwijderen. De signatuur verwisselt het pictogram en de open-vlag voor een uitlijningswaarde (justification value)

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

Hier is de rechthoek belangrijker dan voor een tekstnotitie, omdat de tekst erin wordt omgewikkeld (wraps) en uitgelijnd. Als u het vak te kort maakt, wordt de tekst aan de onderkant afgeknipt; te smal en het wikkelt op plaatsen die u niet had bedoeld. De uitlijning komt van THPDFFreeTextAnnotationJust en heeft slechts de drie waarden. Omdat vrije tekst een markeringsannotatie is, kan een lezer die het bestand in een editor opent, deze selecteren, verplaatsen of verwijderen als een eenheid, wat het verschil is dat bepaalt of u naar vrije tekst grijpt of de woorden gewoon tekent met TextOut. Als het label permanent moet zijn, teken het dan. Als het redactioneel is en bedoeld is om eraf te komen, maak er dan een annotatie van

Geometrische en lijnmarkeringen om ergens naar te wijzen

Vierkanten, cirkels en lijnen zijn de markeringen die u gebruikt om naar een gebied te wijzen in plaats van het in woorden te beschrijven. AddCircleSquareAnnotation dekt de twee doosvormen via een THPDFCSAnnotationType van csCircle of csSquare, waarbij de rechthoek de grenzen van de vorm aangeeft

// 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;

Merk op dat de lijnannotatie het rechthoekpatroon doorbreekt: het neemt twee THPDFCurrPoint records, een begin en een eind, omdat een lijn wordt gedefinieerd door zijn eindpunten, niet door een begrenzingskader (bounding box). De kleur bepaalt de streek (stroke). Als u pijlpunten wilt, heeft HotPDF overloads (overbelastingen) van AddLineAnnotation die stijlen voor lijneinden accepteren, maar de eenvoudige vorm met drie argumenten tekent een kale lijn, wat meestal is wat een callout vereist

Subtypen voor tekstmarkering (text-markup) werken op een gebied dat u al hebt opgemaakt. AddHighlightAnnotation neemt een rechthoek, optionele inhoud en een kleur die standaard geel is, en kleurt het gebied op de manier van een markeerstift. Het is bedoeld om over echte tekst te liggen, dus de rechthoek moet overeenkomen met de grenzen van de woorden die u hebt getekend, wat betekent dat u deze over het algemeen berekent op basis van dezelfde coördinaten die u aan TextOut hebt doorgegeven, in plaats van te gokken

Stempels zijn afhankelijk van de viewer om ze te renderen

Een stempelannotatie is de annotatie die waarschijnlijk het meest zal verschillen van de ene lezer tot de volgende, en de reden is de moeite waard om te begrijpen. AddStampAnnotation benoemt een standaardstempel via THPDFStampAnnotationType, met waarden als satApproved, satConfidential, satFinal, satDraft en satForComment

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

De stempelnaam is een verzoek. PDF definieert de set van standaard stempelnamen, maar niet de kunst erachter, dus elke viewer levert zijn eigen weergave (rendering) van "APPROVED" of "CONFIDENTIAL," en enkelen renderen helemaal niets voor namen die ze niet herkennen. De rechthoek bepaalt het vak waarin de kunst wordt geschaald, en de kleur is een hint die de viewer wel of niet kan respecteren. Als een stempel er overal identiek uit moet zien, is de betrouwbare route helemaal geen standaardstempel: teken de markering zelf met TextOut en de tekenaanroepen, of plaats deze als een vrije tekstannotatie waarvan u het uiterlijk beheert. Grijp naar de standaardstempel wanneer u de vertrouwde look van de viewer wilt en de variatie kunt tolereren

Bestandsbijlagen volgen dezelfde rechthoek-plus-payload vorm. AddFileAttachmentAnnotation neemt de beschrijving, het pad van het in te sluiten bestand, een rechthoek voor het paperclip-pictogram en een kleur. Het bestand reist mee in de PDF, en het pictogram is de hendel (handle) die een lezer gebruikt om het uit te pakken

Hoe annotaties verschillen van AcroForm-velden

De verwarring die de meeste tijd kost, is het behandelen van een annotatie alsof het een formulierveld (form field) is. Beide hechten aan de pagina via /Annots, en een formulierveld is in feite een speciaal annotatiesubtype (een widget), wat de reden is waarom ze gerelateerd lijken. Ze zijn niet inwisselbaar. Een formulierveld bevat een waarde, heeft een naam, neemt deel aan de tabvolgorde (tab order) en kan worden ingediend, gereset of gescript; u maakt die aan met de AddTextField, AddCheckBox en AddPushButton aanroepen, niet de annotatie-aanroepen op deze pagina. Een markeringsannotatie bevat een opmerking of een vorm, heeft geen waarde om in te dienen en is het verkeerde hulpmiddel zodra u input moet verzamelen

De praktische test is eenvoudig. Als het de bedoeling is dat een gebruiker typt, kiest of klikt en het document dit onthoudt, wilt u een AcroForm-veld. Als u een notitie achterlaat, een gebied markeert of een status stempelt die met het bestand meereist maar geen gegevens (data) is, wilt u een annotatie. Het door elkaar halen ervan levert documenten op die er goed uitzien en zich verkeerd gedragen: een "veld" dat niemand kan invullen, of een opmerking die verdwijnt wanneer een formulier wordt gereset. De interactieve kant, met veldtypen, validatie en verzendacties (submit actions), is zijn eigen onderwerp en wordt behandeld in de AcroForm-velden en acties walkthrough

Een pagina samenstellen

De stukken worden samengesteld op de manier waarop de rest van HotPDF dat doet. Stel documenteigenschappen in, roep BeginDoc aan, teken welke pagina-inhoud u maar nodig heeft met de tekst- en grafische aanroepen, voeg annotaties bovenop toe en sluit af met EndDoc. Annotaties hechten zich aan CurrentPage, dus na een AddPage landen ze op de nieuwe pagina, en een notitie die u voor pagina één bedoelde, zal stilletjes op pagina twee verschijnen als u deze toevoegt na het pagina-einde (break)

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;

Een laatste reflex die de moeite waard is om op te bouwen wanneer de uitvoer er verkeerd uitziet: open het bestand in meer dan één viewer voordat u beslist dat de code stuk is. Stempels en de zeldzamere notitiepictogrammen zijn de gebruikelijke boosdoeners, en omdat de annotatie een verzoek aan de lezer is in plaats van geschilderde pixels, is een verschil tussen Acrobat en een lichtgewicht viewer vaak de specificatie (spec) die werkt zoals ontworpen, en geen bug in uw aanroep

De annotatie-aanroepen die hier worden getoond, maken deel uit van de HotPDF-component voor Delphi en C++Builder