Techninis straipsnis

Deklaratyvus PDF maketavimas Delphi aplinkoje su žymėtu rezultatu

HotPDF gali sukurti puslapiuotą dokumentą iš deklaratyvaus medžio, o ne iš koordinačių. Sudarote THPDFDOMDocument iš skyrių, dėklų, teksto, sąrašų ir lentelių, perduodate jį THPDFDOMRenderer, o atvaizduotojas matuoja, puslapiuoja, piešia puslapio elementus ir, jei paprašoma, generuoja PDF/UA struktūros medį, kuris rezultatą padaro prieinamą. Maketo kodas niekada neapskaičiuoja y koordinatės

Kiekvienas, kas prižiūrėjo koordinatėmis grįstą ataskaitų generatorių, žino, kodėl tai svarbu. Pirma versija veikia. Tada kliento adresas išauga iki trijų eilučių, lentelė gauna daugiau eilučių, lokalizuota antraštė persikelia į kitą eilutę, ir kiekviena tolesnė y pozicija tampa neteisinga. Pataisymai kaupiasi kaip rankiniu būdu patikrinami puslapių lūžiai, išsibarstę po verslo logiką, o po dvejų metų atsiradęs žymėto PDF reikalavimas negali būti pritaikytas atgaline data kodui, kuris net nežino, kas yra pastraipa

Ką valdo medis ir kodėl nuosavybė yra griežta

DOM kiekviename lygyje užtikrina vienintelę nuosavybę: dokumentas valdo savo skyrius, skyrius valdo savo turinį, antraštę ir poraštę, o dėklai, konteineriai ir lentelės valdo savo vaikinius elementus. Pakartotinis naudojimas vyksta per Clone arba per registruotą gamyklą, niekada neprijungiant to paties objekto prie dviejų tėvinių elementų. Ši taisyklė nėra formalumas. Komponentas, pasirodantis medyje du kartus, būtų matuojamas du kartus su skirtingais apribojimais ir atlaisvintas du kartus išardymo metu

Praktinė pasekmė kviečiančiam kodui – pagalbinės funkcijos grąžina naujus egzempliorius. Gamyklos registravimas su RegisterComponent ir CreateComponent iškvietimas suteikia pavadintą receptą, kuris kiekvieną kartą sukuria naują komponentą – taip medyje atsiduria pasikartojantys elementai, tokie kaip parašo blokas ar teisinė poraštė

uses
  HPDFDoc, HPDFLayoutDOM;

var
  Doc: THPDFDOMDocument;
  Section: THPDFDOMSection;
  Table: THPDFDOMTable;
  Row: THPDFDOMTableRow;
  I: Integer;
begin
  Doc := THPDFDOMDocument.Create;
  Doc.GenerateStructure := True;        // generuoti PDF/UA struktūros medį
  Doc.Language := 'en-US';

  Section := Doc.AddSection;
  Section.PageWidth := 595;           // A4 punktais
  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);                 // svoriai, ne absoliutūs pločiai
  Table.AddColumn(1);
  Table.AddColumn(1);
  Table.RepeatHeaders := True;
  Row := Table.AddRow(18, True);      // antraštės eilutė
  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;

Kaip puslapiavimas išvengia kvadratinės kainos?

Naivus būdas puslapiuoti medį – klonuoti viską, kas netilpo, ir perkelti į kitą puslapį. Lentelėje su dešimt tūkstančių eilučių tai klonuoja likusias eilutes po vieną kartą kiekvienam puslapiui ir paverčia linijinį dokumentą kvadratiniu

HotPDF vietoj to skaido siaurai. Aukščiausio lygio atvaizduotojas apeina turinio vaikinius elementus pagal indeksą ir niekada neklonuoja viso skyriaus ar turinio. Klonuojamas tik įdėtų dėklų ir konteinerių paveiktas pomedis, jei jie iš tikrųjų kerta puslapio ribą, o du „sunkūs“ lapo tipai neša žymeklį, o ne kopiją: teksto tęsinys saugo šaltinio simbolių diapazoną, kurį dar liko atvaizduoti, o lentelės tęsinys saugo eilučių dalį, kurios dar reikia patalpinti. Ilgi dokumentai lieka linijiniai, o ilgos pastraipos kainuoja tiek pat, ar jos lūžta vieną kartą, ar penkis kartus

Matavimas išlieka sąžiningas šalutinio poveikio atžvilgiu. THPDFLayoutElement.Measure privalo neturėti piešimo šalutinio poveikio, o faktinis patalpinimas visada vyksta per THotPDF.PlaceLayoutElement – tą pačią centrinę procedūrą, kuri iš naujo pamatuoja patalpintą fragmentą, sutvarko perpildymo nuosavybę ir įrašo diagnostiką. DOM atvaizduotojas sprendžia tik naujo puslapio politiką, puslapio elementus, tarpus ir tęsinių gyvavimo trukmę

Lentelės antraštės taisyklės, saugančios nuo begalinio dokumento

Kartotinių lentelės antraščių rodymas per puslapius skamba paprastai, tačiau slepia du gedimo scenarijus. HotPDF reikalauja, kad antraštės eilutės pasirodytų tik pirmajame nuosekliame eilučių bloke ir kad pirmasis skaidymas sutalpintų visas antraštės eilutes plius bent vieną turinio eilutę. Be antrosios taisyklės, antraštė, aukštesnė už likusią vietą, sukurtų puslapį, kuriame yra tik antraštė, po jo – kitą tapatų puslapį, ir taip be galo

Tęsinio puslapiai perpiešia antraštę, ir ta perpiešta kopija pažymima kaip artefaktas, o ne kaip turinys – tai teisingas atsakymas ir prieinamumui, ir teksto ištraukimui. Originali antraštės eilutė lieka loginėje lentelės struktūroje lygiai vieną kartą. Praleidus tai, ekrano skaitytuvas paskelbtų stulpelių pavadinimus dar kartą duomenų viduryje, o teksto ištraukimo įrankis įterptų pasikartojančią antraštės eilutę tarp turinio eilučių

Taip pat yra apsauginė riba tęsinio gyliui, nes pasirinktinis komponentas gali laisvai įgyvendinti Split taip, kad visada grąžintų lygiavertę uodegą. Atvaizduotojas patikrina ribą atskyręs uodegą ir prieš pradėdamas kitą puslapį, o dabartinė iteracija atlaisvina uodegą savo finally bloke, todėl blogai besielgiantis trečiosios šalies komponentas sužlunga su diagnozuojama klaida, o ne prigrūda diską

Vienas loginis elementas, daug puslapio fragmentų

Automatinis žymėjimas yra ta vieta, kur puslapiavimo modelis ir struktūros modelis turi sutapti. Per du puslapius suskaidyta pastraipa yra viena loginė pastraipa, todėl ji turi likti vienu struktūros elementu. Bet pažymėto turinio identifikatoriai priklauso nuo puslapio, todėl kiekvienam matomam fragmentui reikia savo MCID tame puslapyje, kuriame jis pasirodo

HotPDF tai sprendžia palikdama vieną struktūros elementą ir kiekvienam fragmentui pridėdama pažymėto turinio nuorodą į jo /K masyvą, kur pora /Pg ir /MCID nurodo puslapį ir identifikatorių. To MCID ParentTree įrašas rodo atgal į tą patį elementą. Būtent to ir tikisi ISO 14289, ir būtent todėl vidiniai tęsinio klonai skiriasi nuo įprastų klonų: įprastas Clone reiškia naują loginį turinį ir gauna naują semantinę tapatybę, o vidinis tęsinio klonas paveldi to komponento, kurį jis tęsia, tapatybę

Elementų pakartotinis naudojimas ieškomas per semantinių tapatybių indeksą, surikiuotą pagal komponento rodyklę ir ieškomą dvejetaine paieška – tai palaiko logaritminę paieškos trukmę dideliuose medžiuose. Indeksas laiko tik nuorodas, nesančias savininku; pačių struktūros objektų gyvavimo trukmė lieka susieta su PDF objektų grafu

Struktūros taisyklės, kurias atvaizduotojas taiko iš anksto

Įjungus GenerateStructure, kelios PDF/UA taisyklės patikrinamos medžio atvaizdavimo metu, o ne po to, kai failas jau sukurtas. Antraštės prasideda 1 lygiu ir negali praleisti lygių. LI gali pasirodyti tik L viduje, o Lbl ir LBody – tik LI viduje. TR priklauso lentelei, o TH ir TD – eilutei. Iliustracija be alternatyvaus teksto PDF/UA režime atmetama

Atmesti iš anksto – tai sąmoningas pasirinkimas. Validatorius, praneša apie trūkstamą alternatyvų tekstą jau parašius dokumentą, tik pasako, kad partiją iš dešimt tūkstančių ataskaitų reikia generuoti iš naujo; atvaizduotojas, kuris atmeta komponentą, pasako, kuris konkrečiai komponentas, kol duomenys, iš kurių jis buvo sukurtas, dar pasiekiami. Atitikties patikrinimas vis tiek priklauso konvejeryje kaip atskiras žingsnis, o jo mechanika aprašyta straipsnyje PDF/A, PDF/X ir PDF/UA validavimas

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;

Statistikos įrašas naudingesnis, nei atrodo iš pirmo žvilgsnio. Staigiai kylantis SplitCount po šablono pakeitimo dažniausiai reiškia, kad komponentas pradėjo matuotis aukštesnis nei jo konteineris. Palaipsniui kylantis MaximumContinuationDepth yra ankstyvas įspėjimas apie komponentą, kurio Split per puslapį pasistūmi pernelyg mažai. O ArtifactCount palyginimas su tęsinio puslapių skaičiumi patvirtina, kad pasikartojančios antraštės iš tikrųjų buvo pažymėtos kaip artefaktai

Kur DOM tinka šalia tiesioginio API

DOM nepakeičia tiesioginio piešimo; jis veikia virš tų pačių puslapio objektų. Bet ką, ką patalpina atvaizduotojas, galima pertraukti tiesioginiais THotPDF iškvietimais, o tai svarbu, kai ataskaitai reikia vieno rankiniu būdu pozicionuoto elemento, pavyzdžiui, parašo vaizdo tikslioje vietoje. Puslapio uždarymas lieka AddPage ir EndDoc kontrolėje, todėl tiesioginio išpylimo režimas atmintyje nelaiko jokių užbaigtų puslapių, o rezidenti atmintis lieka valdoma dabartinių tęsinių, šriftų resursų ir įprasto dokumento objektų grafo

Rinkitės DOM, kai turinys yra duomenimis grįstas, o maketas – taisyklėmis grįstas, ir palikite tiesioginį piešimą fiksuotai grafikai. Jei jūsų dabartinis skausmas yra konkrečiai lentelių puslapiavimas, verta pirmiau paskaityti siauresnį požiūrį straipsnyje lentelių generavimas PDF faile, o teksto lygmens elgsena, tokia kaip lygiavimas, aprašyta straipsnyje teksto lygiavimas

Deklaratyvus maketavimas, automatinis žymėjimas ir tiesioginio piešimo API pateikiami tame pačiame komponente Delphi ir C++Builder aplinkoms; pilnas funkcijų sąrašas pateikiamas HotPDF Delphi PDF komponento puslapyje