Tekninen artikkeli

Deklaratiivinen PDF-asettelu Delphissä merkityllä tulosteella

HotPDF pystyy rakentamaan sivutetun dokumentin deklaratiivisesta puusta koordinaattien sijaan. Kootaan THPDFDOMDocument-objekti osioista, pinoista, tekstistä, listoista ja taulukoista, annetaan se luokalle THPDFDOMRenderer, ja renderöijä mittaa, sivuttaa, piirtää sivun kalusteet ja pyydettäessä tuottaa PDF/UA-rakennepuun, joka tekee tuloksesta esteettömän. Asettelukoodi ei koskaan laske y-koordinaattia

Jokainen, joka on ylläpitänyt koordinaattivetoista raporttigeneraattoria, tietää, miksi tämä on tärkeää. Ensimmäinen versio toimii. Sitten asiakkaan osoite kasvaa kolmeen riviin, taulukko saa lisää rivejä, lokalisoitu otsikko rivittyy, ja jokainen sen jälkeinen y-sijainti on väärä. Korjaukset kasautuvat manuaalisiksi sivunvaihtotarkistuksiksi ympäri liiketoimintalogiikkaa, eikä kaksi vuotta myöhemmin saapuvaa merkityn PDF:n vaatimusta voi jälkiasentaa koodiin, jolla ei ole aavistustakaan siitä, mikä kappale on

Mitä puu omistaa ja miksi omistus on tiukkaa

DOM pakottaa yksinomistuksen jokaisella tasolla: dokumentti omistaa osionsa, osio omistaa rungon, ylätunnisteen ja alatunnisteen, ja pinot, säiliöt ja taulukot omistavat lapsensa. Uudelleenkäyttö tapahtuu Clone-metodin tai rekisteröidyn tehtaan kautta, ei koskaan liittämällä samaa objektia kahteen vanhempaan. Tämä sääntö ei ole muodollisuutta. Komponentti, joka esiintyisi puussa kahdesti, mitattaisiin kahdesti eri rajoitteilla ja vapautettaisiin kahdesti purkamisen yhteydessä

Kutsuvalle koodille tämän käytännön seuraus on se, että apufunktiot palauttavat uusia instansseja. Tehtaan rekisteröinti RegisterComponent-metodilla ja CreateComponent-metodin kutsuminen antaa nimetyn reseptin, joka tuottaa tuoreen komponentin joka kerta, ja näin toistuva kalustus, kuten allekirjoituslohko tai lakisääteinen alatunniste, kuuluu puuhun

uses
  HPDFDoc, HPDFLayoutDOM;

var
  Doc: THPDFDOMDocument;
  Section: THPDFDOMSection;
  Table: THPDFDOMTable;
  Row: THPDFDOMTableRow;
  I: Integer;
begin
  Doc := THPDFDOMDocument.Create;
  Doc.GenerateStructure := True;        // tuota PDF/UA-rakennepuu
  Doc.Language := 'en-US';

  Section := Doc.AddSection;
  Section.PageWidth := 595;           // A4 pisteinä
  Section.PageHeight := 842;
  Section.MarginLeft := 56;
  Section.MarginTop := 56;
  Section.MarginRight := 56;
  Section.MarginBottom := 56;
  Section.Style.FontName := 'Helvetica';
  Section.Style.FontSize := 10;

  Section.Body.AddHeading('Annual maintenance report', 1);
  Section.Body.AddText('Every asset inspected during the reporting ' +
    'period is listed below, grouped by site.');
  Section.Body.AddSpacer(12);

  Table := THPDFDOMTable.Create('assets');
  Table.AddColumn(3);                 // painot, ei absoluuttisia leveyksiä
  Table.AddColumn(1);
  Table.AddColumn(1);
  Table.RepeatHeaders := True;
  Row := Table.AddRow(18, True);      // otsikkorivi
  Row[0].Text := 'Asset';
  Row[1].Text := 'Last service';
  Row[2].Text := 'Status';
  for I := 0 to High(Assets) do
  begin
    Row := Table.AddRow(16);
    Row[0].Text := Assets[I].Name;
    Row[1].Text := Assets[I].ServiceDate;
    Row[2].Text := Assets[I].Status;
  end;
  Section.Body.Add(Table);
end;

Miten sivutus välttää neliöllisen kustannuksen?

Naiivi tapa sivuttaa puu on kloonata kaikki, mikä ei mahtunut, ja siirtää se seuraavalle sivulle. Taulukossa, jossa on kymmenentuhatta riviä, tämä kloonaa jäljellä olevat rivit kerran sivua kohden ja muuttaa lineaarisen dokumentin neliölliseksi

HotPDF pilkkoo sen sijaan kapeasti. Ylätason renderöijä käy läpi rungon lapset indeksin mukaan eikä koskaan kloonaa koko osiota tai runkoa. Vain sisäkkäiset pinot ja säiliöt, jotka todella hajaantuvat sivun rajan yli, saavat vaikutuksenalaisen alipuunsa kloonattua, ja kaksi raskasta lehtityyppiä kantavat kursoria kopion sijaan: tekstin jatke tallentaa lähdemerkkialueen, jonka se vielä on velkaa, ja taulukon jatke tallentaa rivisiivun, jota se ei ole vielä sijoittanut. Pitkät dokumentit pysyvät lineaarisina, ja pitkät kappaleet maksavat saman verran riippumatta siitä, katkeavatko ne kerran vai viisi kertaa

Mittaus pysyy rehellisenä sivuvaikutuksista. THPDFLayoutElement.Measure-metodin vaaditaan olevan vapaa piirtämisen sivuvaikutuksista, ja todellinen sijoittelu kulkee aina luokan THotPDF.PlaceLayoutElement kautta, saman keskusrutiinin, joka mittaa sijoitetun fragmentin uudelleen, asettaa ylivuoto-omistuksen ja kirjaa diagnostiikkaa. DOM-renderöijä päättää vain uuden sivun käytännön, sivun kalusteet, välistyksen ja jatkeiden elinajan

Taulukon otsikkosäännöt, jotka estävät loputtoman dokumentin

Taulukon otsikoiden toistaminen sivujen yli kuulostaa yksinkertaiselta ja kätkee kaksi virhetilaa. HotPDF vaatii, että otsikkorivit esiintyvät vain ensimmäisessä peräkkäisten rivien jaksossa, ja että ensimmäiseen jakoon mahtuu kaikki otsikkorivit sekä vähintään yksi rivi runkoa. Ilman toista sääntöä jäljellä olevaa tilaa korkeampi otsikko tuottaisi sivun, joka sisältää vain otsikon, ja sen jälkeen toisen identtisen sivun, loputtomasti

Jatkosivut piirtävät otsikon uudelleen, ja tämä uudelleenpiirretty kopio merkitään artefaktiksi eikä sisällöksi, mikä on oikea ratkaisu sekä esteettömyyden että tekstin poiminnan kannalta. Alkuperäinen otsikkorivi pysyy loogisessa taulukkorakenteessa täsmälleen kerran. Ohita tämä, ja ruudunlukija ilmoittaa sarakeotsikot uudelleen kesken tietojen, ja tekstipoimin lisää kaksoiskappaleen otsikkorivistä runkorivien väliin

Jatkeiden syvyydellä on myös puolustava katto, koska mukautettu komponentti voi vapaasti toteuttaa Split-metodin niin, että se palauttaa aina vastaavan hännän. Renderöijä tarkistaa rajan hännän irrottamisen jälkeen ja ennen seuraavan sivun aloittamista, ja nykyinen iteraatio vapauttaa hännän omassa finally-lohkossaan, joten huonosti käyttäytyvä kolmannen osapuolen komponentti epäonnistuu diagnosoitavalla virheellä levyn täyttämisen sijaan

Yksi looginen elementti, monta sivufragmenttia

Automaattinen merkintä on kohta, jossa sivutusmallin ja rakennemallin on oltava yhtä mieltä. Kahden sivun yli katkeava kappale on yksi looginen kappale, joten sen on pysyttävä yhtenä rakenne-elementtinä. Mutta merkityn sisällön tunnisteet ovat sivukohtaisia, joten jokainen näkyvä fragmentti tarvitsee oman MCID-tunnisteensa sillä sivulla, jolla se esiintyy

HotPDF ratkaisee tämän säilyttämällä yhden rakenne-elementin ja liittämällä merkityn sisällön viittauksen sen /K-taulukkoon kutakin fragmenttia varten, jossa /Pg- ja /MCID-pari tunnistaa sivun ja tunnisteen. Kyseisen MCID:n ParentTree-paikka osoittaa takaisin samaan elementtiin. Tämä on juuri sitä, mitä ISO 14289 odottaa, ja tästä syystä jatkekloonit eroavat tavallisista klooneista: tavallinen Clone tarkoittaa uutta loogista sisältöä ja saa uuden semanttisen identiteetin, kun taas sisäinen jatkeklooni perii sen komponentin identiteetin, jota se jatkaa

Elementtien uudelleenkäyttöä haetaan komponentin osoittimen mukaan lajitellusta semanttisten identiteettien hakemistosta, jota haetaan binäärivertailulla, mikä pitää haun logaritmisena suurissa puissa. Hakemisto sisältää vain ei-omistavia viittauksia; itse rakenneobjektien elinaika pysyy PDF-objektigraafin hallussa

Rakennesäännöt, joita renderöijä valvoo etukäteen

Kun GenerateStructure on käytössä, useita PDF/UA-sääntöjä tarkistetaan puuta renderöitäessä sen sijaan, että ne tarkistettaisiin tiedoston olemassaolon jälkeen. Otsikot alkavat tasolta 1 eivätkä saa hypätä tasoja. LI voi esiintyä vain elementin L sisällä, ja Lbl sekä LBody vain elementin LI sisällä. TR kuuluu taulukkoon, ja TH sekä TD kuuluvat riviin. Kuva ilman vaihtoehtoista tekstiä hylätään PDF/UA-tilassa

Varhainen hylkääminen on tässä tietoinen valinta. Validaattori, joka raportoi puuttuvasta vaihtoehtoisesta tekstistä sen jälkeen, kun dokumentti on kirjoitettu, kertoo, että kymmenentuhannen tiliotteen erä täytyy tehdä uudelleen; renderöijä, joka hylkää komponentin, kertoo, mikä komponentti, samalla kun sen tuottanut data on vielä ulottuvilla. Vaatimustenmukaisuuden todentaminen kuuluu silti putkeen erillisenä vaiheena, ja sen mekaniikka käsitellään artikkelissa PDF/A-, PDF/X- ja PDF/UA-validointi

var
  Pdf: THotPDF;
  Renderer: THPDFDOMRenderer;
  Stats: THPDFDOMRenderStatistics;
begin
  Pdf := THotPDF.Create(nil);
  Renderer := THPDFDOMRenderer.Create;
  try
    Pdf.FileName := 'maintenance-report.pdf';
    Pdf.BeginDoc;
    Stats := Renderer.Render(Doc, Pdf);
    Pdf.EndDoc;

    Writeln(Format('%d page(s), %d placement(s), %d split(s)',
      [Stats.PageCount, Stats.PlacementCount, Stats.SplitCount]));
    Writeln(Format('structure elements=%d marked content=%d artifacts=%d',
      [Stats.StructureElementCount, Stats.MarkedContentCount,
       Stats.ArtifactCount]));
    Writeln(Format('deepest continuation chain: %d',
      [Stats.MaximumContinuationDepth]));
  finally
    Renderer.Free;
    Doc.Free;
    Pdf.Free;
  end;
end;

Tilastotietue on hyödyllisempi kuin miltä se ensin näyttää. SplitCount-arvon jyrkkä nousu mallin muutoksen jälkeen tarkoittaa yleensä sitä, että jokin komponentti alkoi mitata itsensä säiliötään korkeammaksi. MaximumContinuationDepth-arvon hidas nousu on varhainen varoitus komponentista, jonka Split-metodi etenee liian vähän sivua kohden. Ja ArtifactCount-arvon vertaaminen jatkosivujen määrään vahvistaa, että toistetut otsikot todella merkittiin artefakteiksi

Mihin DOM sopii suoran API:n rinnalle

DOM ei korvaa suoraa piirtämistä; se istuu samojen sivuobjektien päällä. Kaiken, minkä renderöijä sijoittaa, voi lomittaa suorien THotPDF-kutsujen kanssa, millä on merkitystä, kun raportti tarvitsee yhden käsin sijoitetun elementin, kuten allekirjoituskuvan, tarkkaan sijaintiin. Sivujen sulkeminen pysyy metodien AddPage ja EndDoc hallinnassa, joten välitön tyhjennystila ei pidä muistissa valmiita sivuja, ja resident-muisti pysyy nykyisten jatkeiden, fonttiresurssien ja tavallisen dokumenttiobjektigraafin hallinnassa

Valitse DOM, kun sisältö on datavetoista ja asettelu sääntövetoista, ja säilytä suora piirtäminen kiinteälle grafiikalle. Jos nykyinen ongelmasi koskee nimenomaan taulukon sivutusta, kapeampi lähestymistapa artikkelissa taulukoiden luonti PDF:ään kannattaa lukea ensin, ja tekstitason käyttäytyminen, kuten tasaus, kuvataan artikkelissa tekstin tasaus

Deklaratiivinen asettelu, automaattinen merkintä ja suora piirto-API toimitetaan samassa komponentissa Delphille ja C++Builderille; täydellinen ominaisuusluettelo on sivulla HotPDF Delphi PDF -komponentin sivulla