Tekninen artikkeli

Ei-tuhoava PDF-korostus Delphissä: HotPDF-tarkistuskerros

Kappaleen ympärille tarkistuksen aikana piirretyn suorakulmion ei tarvitse muuttua merkinnäksi PDF:n sisällä. HotPDF:n THPDFViewerModel paljastaa AddHighlightRegion-metodin, joka pitää jokaisen korostuksen muistinvaraisena tietueena sen sijaan, että se olisi muutos ladattuun asiakirjaan, joten tarkistaja voi merkitä kymmeniä sivuja, samalla kun levyllä oleva tiedosto pysyy tavu tavulta samana kuin ennen. Zoomaa 6400 %:iin, kierrä sivua 90 astetta, vaihda Fit Width -tilasta Fit Page -tilaan, ja sama suorakulmio osuu silti samaan kappaleeseen, koska koordinaattilaskenta kulkee todellisen renderöintigeometrian kautta sillä hetkellä, kun merkintä piirrettiin

PDF-katseluohjelman ympärille rakennettu tarkistustyökalu törmää tähän ongelmaan jatkuvasti. Punakynäruutu, laadunvarmistuskierros luoduille laskuille, sisäinen hyväksymistyönkulku: kaikki nämä tarvitsevat tavan antaa jonkun kiinnittää huomiota sivun alueeseen ilman, että jokaisesta luonnosmerkinnästä tulee pysyvä muutos tiedostoon, ja ilman että tarvitsee turvautua täyteen annotaatiojärjestelmään vain näyttääkseen värillisen laatikon, kun joku vielä miettii, kuuluuko merkintä sinne. HotPDF vastaa tähän omistetulla korostuskerroksella, joka istuu kokonaan Model-puolella jaossa, joka kuvataan artikkelissa mukautetun PDF-katseluohjelman rakentaminen MVC-arkkitehtuurilla Delphissä, minkä vuoksi sama korostuslista voidaan myös ajaa yksikkötestistä ilman ainuttakaan ikkunakahvaa näkyvissä

Mitä HotPDF:n AddHighlightRegion todella tallentaa?

AddHighlightRegion tallentaa täsmälleen kolme asiaa merkintää kohden: nollapohjaisen sivuindeksin, THPDFRectangle-suorakulmion PDF:n käyttäjätilan koordinaateissa ja TColor-värin, kaikki pakattuna THPDFViewerHighlight-tietueeksi THPDFViewerModel:n sisällä. Viewer.HighlightRegion(PageIndex, PageRect, clYellow)-kutsu, tai vastaava Model.AddHighlightRegion, lisää yhden näistä tietueista yksityiseen taulukkoon ja palauttaa sen indeksin, ja tuo indeksi on ainoa kahva, jonka kutsuja saa takaisin: erillistä oliota ei ole, ei viittauslaskettua rajapintaa, ei mitään vapautettavaa. Kaikki muut tämän artikkelin ominaisuudet, merkinnän piirtäminen, sen uudelleenkartoitus zoomauksen muutoksen jälkeen, sen poistaminen, on rakennettu tuon yhden pienen tietueen päälle

Jokainen suorakulmio normalisoidaan ja rajataan ennen hyväksymistä. AddHighlightRegion vaihtaa vasemman ja oikean reunan keskenään, jos tarkistaja vetää oikealta vasemmalle, vaihtaa ylä- ja alareunan ylöspäin suuntautuvassa vedossa, ja rajaa sitten tuloksen sivun MediaBoxia vasten, joka haetaan funktiolla GetLoadedPageBox. Suorakulmio, jonka leveys tai korkeus päätyy nollaksi tai joka on kokonaan sivun ulkopuolella, hylätään suoralta kädeltä: metodi palauttaa arvon -1, eikä listaan lisätä mitään. Tuo paluuarvo ei ole koristeellinen: ulkoisesta tarkistustiedostosta uudelleenrakennettu korostuserä, tai vanhentuneet koordinaatit sivun vaihdon jälkeen, voivat hiljaisesti menettää merkintöjä, jos kutsuja ei tarkista sitä

Miten korostus pysyy kohdallaan zoomauksen tai kierron jälkeen?

Korostus pysyy kohdallaan, koska HotPDF tallentaa sen PDF-sivutilassa ja projisoi sen uudelleen näyttötilaan jokaisella uudelleenpiirrolla, sen sijaan että tallentaisi näyttösuorakulmion, joka vanhenisi heti zoomaustason muuttuessa. THPDFViewerModel.PagePointToView ja sen käänteinen ViewPointToPage tekevät tämän projektion kahdessa vaiheessa: ensin sivun omaa /Rotate-kenttää, sitten katseluohjelman itsenäistä ViewRotation-arvoa, jota ei koskaan kirjoiteta takaisin PDF:ään ja joka vaikuttaa vain siihen, mitä katseluohjelma näyttää. Muunnoksen purkaminen hiiren painikkeen vapautuksen yhteydessä ajaa saman kaksi vaihetta käänteisessä järjestyksessä, mikä on juuri se, mikä saa korkealla zoomaustasolla 270 astetta kierretylle sivulle piirretyn korostuksen osumaan täsmälleen oikeaan paikkaan sen jälkeen, kun tarkistaja palauttaa näkymän takaisin Fit Page -tilaan

Tuohon projektioon käytetty DPI on yhtä tärkeä kuin kierto. HotPDF:n katseluohjelma tallentaa juuri sillä hetkellä näytöllä olevan bittikartan tarkan DPI:n kenttään FRenderedDPI heti jokaisen renderöinnin jälkeen, ja ImageMouseUp välittää saman arvon funktiolle ViewPointToPage, jotta hiirikoordinaatti muunnetaan aina sillä resoluutiolla, jolla se todella piirrettiin, ei resoluutiolla, joka lasketaan uudelleen nykyisestä zoomausominaisuudesta. CreatePageSnapshot ja sen sukulaiset rajoittavat DPI:n välille 12–2400, mutta interaktiivisella renderöintipolulla ei ole tällaista kattoa: vakiozoomausasteikko huipentuu 6400 %:iin, mikä laskee hyvin yli 2400 DPI:n oletusarvoisella 96 DPI:n perustasolla, joten tuollaisen tilannekuvatyylisen rajan uudelleenkäyttö koordinaattikartoituksessa siirtäisi jokaisen korostuksen useilla pikseleillä zoomausalueen yläpäässä. Kaksi pienempää oletusta täydentää vuorovaikutuksen: veto, joka on lyhyempi kuin kaksi pikseliä kummallakin akselilla, tulkitaan klikkaukseksi eikä tuota korostusta, ja korostaminen ei voi alkaa ennen kuin ainakin yksi sivu on todella renderöity, koska FRenderedDPI alkaa nollasta

Interaktiivisen korostuksen kytkeminen tarkistusnäyttöön

Interaktiivisen korostuksen kytkeminen päälle on kolmen ominaisuuden tehtävä itse THPDFViewer-ohjausobjektissa: aseta InteractionMode-arvoksi vimHighlight oletusarvon vimBrowse sijaan, valitse HighlightColor, jonka oletusarvo on clYellow, ja käsittele OnMarqueeSelect-tapahtuma saadaksesi tietää, mitä tarkistaja juuri piirsi. Kaikki muu, hiiren kaappaus, pisteviivaisen valintasuorakulmion piirtäminen tarkistajan vetäessä, vapautuspisteen muuntaminen takaisin sivutilaan, AddHighlightRegion-kutsu, tapahtuu ohjausobjektin sisällä ennen tuon tapahtuman laukeamista

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 laukeaa vain vedolle, joka todella tuotti korostuksen: klikkaus, joka on liian pieni laskettavaksi vedoksi, tyhjentää valintapäällysteen välittömästi, ja veto, joka päätyy kokonaan sivun ulkopuolelle, saapuu AddHighlightRegion-funktioon mutta hylätään siellä samalla tavalla kuin ohjelmallinen kutsu hylättäisiin, joten tapahtuma pysyy hiljaa kummassakin tapauksessa. Yksi toteutusyksityiskohta kannattaa tuntea, jos korostus vaikuttaa joskus lakkaavan reagoimasta ohjausobjektin reunoilla: hiiren kaappaus kuuluu itse THPDFViewer-oliolle, TScrollBox-jälkeläiselle, ei sisäiselle TImage-oliolle, joka näyttää sivun bittikartan, ja juuri tämä sallii tarkistajan vetää sivun renderöidyn reunan ohi ja silti saada siistin vapautuksen

Korostusten lisääminen, poistaminen ja uudelleenlukeminen koodista

Korostusten ei tarvitse tulla lainkaan hiiren vedosta. Viewer.HighlightRegion(PageIndex, PageRect, Color), joka ohjautuu samaan Model.AddHighlightRegion-metodiin, jota interaktiivinen veto kutsuu sisäisesti, on julkinen nimenomaan siksi, että tarkistusnäyttö voi rakentaa korostukset uudelleen datasta, joka sillä jo on: tietokannasta ladatuista kommenteista, tekstihaun tuloksista tai edellisestä istunnosta palautetuista merkinnöistä. Koska koordinaatit ovat tavallisia PDF:n käyttäjätilan lukuja, mikään tässä polussa ei riipu siitä, että sivu olisi ensin renderöity, toisin kuin interaktiivinen veto, joka tarvitsee kentän FRenderedDPI jo sisältävän todellisen arvon

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;

Yksittäisen korostuksen poistaminen on kohta, jossa taulukkopohjainen tallennus paljastuu. RemoveHighlightRegion poistaa yhden tietueen ja siirtää jokaisen myöhemmän tietueen yhdellä paikalla alaspäin sulkeakseen aukon, mikä tarkoittaa, että mikä tahansa aiemmin talteen otettu indeksi, olipa se OnMarqueeSelect-tapahtumasta tai aiemmasta luetteloinnista, ei ole enää luotettava heti, kun jotain sitä ennen listassa poistetaan. OnHighlightChange laukeaa jokaisella lisäyksellä, poistolla ja ClearHighlightRegions-kutsulla, mutta se ei kanna tietoa siitä, mikä muuttui, joten turvallinen malli on kohdella sitä signaalina rakentaa uudelleen mikä tahansa lista, jota tarkistuspaneeli näyttää, käyttäen HighlightCount- ja TryGetHighlightRegion-metodeja, sen sijaan että paikattaisiin välimuistitettua indeksiä paikallaan

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;

Milloin merkinnästä pitäisi tulla oikea Highlight-annotaatio?

Korostusalueesta pitäisi tulla oikea annotaatio heti, kun sen täytyy säilyä yhden THPDFViewer-instanssin ulkopuolella. HotPDF paljastaa myös AddHighlightAnnotation-metodin uudelle sivulle ja AddLoadedHighlightAnnotation-metodin jo ladatulle asiakirjalle, ja lähes identtisestä nimestä huolimatta tämä on täysin eri mekanismi: molemmat kirjoittavat todellisen ISO 32000-1 §12.5.6.10 -tekstimerkintäannotaation, PDF:n /Subtype /Highlight, sivun /Annots-taulukkoon, /QuadPoints-kentällä merkittynä tarkkaan glyfijaksoon, ja mikä tahansa standardinmukainen PDF-katseluohjelma renderöi sen heti, kun tiedosto on tallennettu, ei vain HotPDF itse. Sama mekanismiraja päättää myös sen, kulkeeko merkintä edestakaisin XFDF:n kautta: AddLoadedHighlightAnnotation-metodilla luotu annotaatio on tavallinen PDF-olio, jonka ExportLoadedAnnotationsToXFDF poimii ja antaa Acrobatille tai muulle tarkistustyökalulle ISO 19444-1 -merkintänä, mikä käsitellään artikkelissa PDF-annotaatioiden tuonti ja vienti XFDF-muodossa Delphissä, kun taas AddHighlightRegion-metodilla lisätty alue on tuolle viennille näkymätön, koska sitä ei koskaan kirjoitettu olio-kaavioon lainkaan: se on olemassa vain niin kauan kuin sen luonut THPDFViewerModel on olemassa. Koko sivulla käytettävissä olevien merkintä- ja geometristen annotaatiotyyppien perhe, ja miten suorakulmio sijoittaa kunkin niistä, käsitellään artikkelissa artikkeli PDF-annotaatioista Delphissä HotPDF:llä, ja käytännön sääntö on yksinkertainen: pidä merkintä kertakäyttöisenä niin kauan kuin asiakirjasta vielä keskustellaan, ja sido se annotaatioksi vasta, kun päätös on lopullinen

Missä korostuskerros pysähtyy

Korostuskerros puolestaan ei yritä näyttää läpikuultavalta korostuskynältä: RefreshDocument piirtää jokaisen alueen kahden pikselin paksuisena ääriviivasuorakulmiona omassa värissään välimuistitetun sivubittikartan päälle, samalla tavalla kuin se piirtää hakuosumat, sen sijaan että se sekoittaisi värillisen täytön alla olevan tekstin päälle, joten klassinen keltainen korostustussi-ilme täytyy piirtää sovelluskoodissa tai jättää ylennetyn annotaation oman ulkoasuvirran varaan. Yksi ominaisuus, joka kannattaa käyttää uudelleen heti kun alue on olemassa, on CreateCurrentPageRegionSnapshot, joka ottaa saman THPDFRectangle-suorakulmion, joka korostuksella jo on, ja renderöi juuri tuon alueen bittikartaksi, hyödyllinen pienen esikatselukuvan liittämiseksi tarkistuskommenttiin ilman koko sivun vientiä. Tarkistussovelluksen ei tarvitse valita kahden mekanismin väliltä etukäteen: aseta jokainen uusi merkintä oletuksena kertakäyttöiseksi THPDFViewerHighlight-alueeksi niin kauan kuin kommenttiketju pysyy avoimena, ja kutsu AddLoadedHighlightAnnotation-metodia vasta, kun tarkistaja ratkaisee sen, mikä pitää ladatun PDF:n koskemattomana sen edestakaisen keskustelun ajan, joka tuottaa eniten vaihtelua. Tässä kuvattu katseluohjelmakomponentti on osa vakiomuotoista HotPDF-komponenttia Delphille ja C++Builderille, yhdessä muun edellä mainitun annotaatio- ja lomake-API:n kanssa