Techninis straipsnis

PDF dokumentų kūrimas nuo nulio su PDFium Component programoje Delphi

PDFium žinomas kaip peržiūros variklis, atvaizdavimo sistema, slypinti už Chrome PDF skirtuko, todėl pirmiausia reikia išsiaiškinti, kad PDFium Component taip pat gali sukurti dokumentą, kuris anksčiau niekada neegzistavo. Kūrimo pusė apgaubia PDFium puslapių objektų API: sukuriate tuščią dokumentą, pridedate tikslių matmenų puslapius ir dedate tekstą, vektorinius kelius bei paveikslėlius į kiekvieną puslapį jūsų pasirinktose koordinatėse. Nereikia mokytis jokios puslapio aprašymo kalbos, nėra ir spausdintuvo tvarkyklės. Jūs iškviečiate metodus, biblioteka surenka PDF objektus, o SaveAs serijuoja rezultatą

Ko jūs negaunate, tai išdėstymo variklio. Tai pakankamai svarbu pasakyti iš anksto, nes tai formuoja kiekvieną žemiau pateiktą pavyzdį. PDFium Component talpina turinį ten, kur jam nurodote, absoliučiose koordinatėse ir niekur kitur. Jis neperkels pastraipos, neperkels teksto per puslapio lūžį ir nesuskaičiuos lentelės iš eilučių ir stulpelių. Tai jūsų darbas. Jei atėjote tikėdamiesi kažko, kas perpina prozą taip, kaip teksto procesorius, susikalibruokite dabar: tai yra tikslus, žemo lygio išdėstymo API, artimesnis piešimui ant drobės nei dokumento rinkimui. Generuojamoms sąskaitoms faktūroms, sertifikatams, etiketėms ir ataskaitų puslapiams, kur jau žinote, kur priklauso kiekvienas elementas, toks tikslumas yra būtent tai, ko jums reikia

Minimumas, norint sukurti failą

Trys iškvietimai skiria tuščią TPdf nuo išsaugoto PDF: sukurkite dokumentą, pridėkite puslapį, įrašykite jį. Viskas kita yra turinys, kurį dedate tarp jų

uses
  Vcl.Graphics,   // for clBlack and TColor
  PDFium;         // TPdf lives here

procedure CreateBlankPdf(const FileName: string);
var
  Pdf: TPdf;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.CreateDocument;                 // empty in-memory document
    Pdf.AddPage(0, 595, 842);           // A4 portrait, in points
    Pdf.AddText('First page', 'Arial', 18, 50, 780);
    Pdf.SaveAs(FileName);               // serialize to disk
  finally
    Pdf.Active := False;
    Pdf.Free;
  end;
end;

Viena detalė klaidina žmones, mačiusius senesnius fragmentus: nepriskirkite Pdf.Active := True po CreateDocument. Active savybė praneša, ar egzistuoja dokumento rodyklė (handle), o CreateDocument jau ją sukūrė, todėl ši savybė yra True tuo momentu, kai tas iškvietimas grįžta. Vėl ją nustatyti geriausiu atveju yra nieko nedarantis veiksmas (no-op), o blogiausiu – klaidina kitą skaitytoją. Active atsiperka išeinant: priskyrus False atlaisvinamas pagrindinis dokumentas prieš Free, kas yra švari uždarymo tvarka. Traktuokite CreateDocument ir failą įkeliantį atidarymą kaip vienas kitą atmetančius veiksmus. Biblioteka atsisako kurti naują dokumentą TPdf objekte, kuris jau turi atidarytą dokumentą, todėl pakartotinis naudojimas reiškia pirmiausia uždaryti dabartinį dokumentą

Koordinatės prasideda apatiniame kairiajame kampe

Antroji argumentų pora AddText, kaip ir kiekvienam išdėstymo iškvietimui, yra taškas PDF vartotojo erdvėje. Pradžios taškas (origin) yra apatiniame kairiajame puslapio kampe, X eina į dešinę, o Y eina į viršų. Vienas vienetas yra vienas taškas, 1/72 colio, taigi A4 formato puslapis yra 595 ant 842 vienetų, o US Letter yra 612 ant 792. Tas į viršų nukreiptas Y yra dažniausias painiavos „mano tekstas ne puslapyje“ šaltinis, nes ekrano ir taškinės grafikos (bitmap) koordinatės pradžios tašką talpina viršuje, o Y didėja žemyn. 842 taškų aukščio puslapyje, antraštė netoli viršaus yra maždaug Y 780, o ne Y 60. Kai tekstas atsiduria netikėtoje vietoje, puslapio aukštis minus jūsų Y beveik visada yra tas skaičius, kurį iš tikrųjų turėjote omenyje

AddPage kaip pirmąjį argumentą priima įterpimo poziciją, išreikštą pradedant vienetu, su 0 kaip patogiu „dokumento pradžios“ trumpiniu. Perduokite 0 arba 1 pirmajam puslapiui ir puslapis bus įterptas priekyje; perduokite reikšmę, atitinkančią puslapių skaičių, prie kurio pridedate, kad pridėtumėte į pabaigą. Naujai pridėtas puslapis taip pat tampa dabartiniu puslapiu, į kurį nukreipiami vėlesni piešimo iškvietimai, todėl po jo pridėjimo nereikia atskiro „pasirinkite šį puslapį“ žingsnio. Jei pridedate kelis puslapius ir vėliau reikia vėl piešti ankstesniame, nustatykite PageNumber, kad perkeltumėte žymeklį; kol pildote puslapius tokia tvarka, kokia juos kuriate, galite palikti jį ramybėje

Teksto rašymas ir šriftų taisyklė, kuri kanda tyliai

AddText signatūra savyje talpina viską, ko reikia vienam išvedimui: eilutę, šrifto pavadinimą, dydį taškais, X ir Y inkarą, tada pasirinktinę spalvą, alfa baitą skaidrumui ir pasukimo kampą laipsniais

procedure WriteHeader(Pdf: TPdf; const Title, Author: string);
begin
  // Title in black, default opacity, no rotation
  Pdf.AddText(Title, 'Arial', 20, 50, 780);
  // A lighter byline 24 points below it
  Pdf.AddText('By ' + Author, 'Arial', 11, 50, 756, clGray);
  // A faint diagonal draft stamp across the page
  Pdf.AddText('DRAFT', 'Arial', 64, 180, 380, clGray, $30, 45.0);
end;

Alfa baitas kinta nuo $00 (nematomas) iki $FF (nepermatomas), o tai ir paverčia juodraščio (draft) antspaudą vandens ženklu, o ne ištisiniu bloku: $30 yra maždaug devyniolikos procentų nepermatomumas, pakankamas, kad būtų galima per jį skaityti. Kampas suka tekstą prieš laikrodžio rodyklę aplink jo inkarą, todėl 45 laipsniai suteikia klasikinį antspaudą iš vieno kampo į kitą. Nė vienam iš jų nereikia atskiros vandens ženklo funkcijos. Vandens ženklas tėra didelis, pusiau skaidrus, pasuktas AddText iškvietimas, o piešiant jį prieš arba po pagrindinio teksto, nusprendžiama, ar jis bus už turinio, ar ant jo

Šriftai nusipelno atidaus sakinio, nes klaidos rėžimas yra tylus. Kai perduodate šrifto pavadinimą, PDFium Component paprašo operacinės sistemos to šrifto TrueType duomenų ir įterpia juos į dokumentą, štai kodėl failas, sukurtas jūsų kompiuteryje, atvaizduojamas identiškai tame, kuriame tas šriftas niekada nebuvo įdiegtas. Kabliukas yra tai, kas nutinka, kai pavadinimas nerandamas: rašybos klaida, arba šriftas tiesiog neegzistuoja kūrimo kompiuteryje. Jokia išimtis (exception) nemetama. Biblioteka pereina prie teksto objekto, kuris neša pavadinimą tik kaip etiketę, be jokio įterpimo, sukūrimo, ir palieka peržiūros programai pakeisti tuo, ką ji laiko artimu. Tekstas pasirodo jūsų testuose, atrodo įtikinamai, ir pakeičia metrikas ar glifus tuo momentu, kai failas atidaromas kur nors kitur, kur įdiegti skirtingi šriftai. Naudokite pavadinimus, apie kuriuos žinote, kad jie yra generuojančiame kompiuteryje, traktuokite šriftų sąrašą kaip diegimo priklausomybę, ir atidarykite pavyzdį peržiūros programoje švarioje sistemoje prieš pasitikėdami rezultatu

Vektorinės figūros: sukurkite kelią, tuomet jį patvirtinkite

Linijos, stačiakampiai ir užpildyti regionai eina per kelią. Jį atidarote su CreatePath, kuris vienu metu nustato pradinį tašką ir visą stilizavimą, užpildymo režimą, užpildo ir brūkšnio (stroke) spalvas su jų pačių alfa baitais, brūkšnio plotį, linijų galus ir sujungimus. Tada jį pratęsiate su LineTo, BezierTo ir ClosePath, o galiausiai AddPath patvirtina baigtą kelią puslapyje. Patvirtinimo žingsnį lengva pamiršti, o jį praleidus nebus sugeneruojama niekas

procedure DrawDivider(Pdf: TPdf; X, Y, Width: Single);
begin
  // A thin horizontal rule. The rectangle overload sets a box directly:
  // X, Y, Width, Height, then fill mode and colors.
  Pdf.CreatePath(X, Y, Width, 0.5, fmNone, clBlack, $FF,
    True, clBlack, $FF, 1.0);
  Pdf.AddPath;
end;

procedure DrawTriangle(Pdf: TPdf);
begin
  // Point overload: start at the first vertex, line to the rest, close.
  Pdf.CreatePath(200, 300, fmWinding, clBlue, $80, True, clNavy, $FF, 2.0);
  Pdf.LineTo(300, 300);
  Pdf.LineTo(250, 400);
  Pdf.ClosePath;
  Pdf.AddPath;          // nothing is drawn until this runs
end;

Dvi perkrovos (overloads) apima dažniausiai pasitaikančius atvejus. Keturių koordinačių forma priima X, Y, plotį ir aukštį bei suteikia ašimis sulygiuotą stačiakampį vienu iškvietimu, o to jūs ir griebiatės norėdami nupiešti liniją, langelio kraštinę ar užpildytą fono skydelį. Dviejų koordinačių forma nustato tik pradinį tašką, o likusią kontūro dalį sekate patys naudodami LineTo ir BezierTo. Užpildymo rėžimas kontroliuoja, kaip piešiami persidengiantys regionai: fmWinding (nenulinis apvyniojimas) tinka daugumai ištisinių figūrų, fmAlternate (lyginis-nelyginis) apdoroja išpjovas ir save kertančius kontūrus, o fmNone palieka tik nubrėžtą kelią be užpildo, ką ir naudoja aukščiau esantis skirtukas

Lentelės yra keliai ir tekstas, surinkti rankiniu būdu

Kadangi nėra lentelės primityvo, lentelė yra ciklas. Jūs nusprendžiate stulpelių X poslinkius ir eilutės aukštį, įrašote kiekvieną langelį naudodami AddText, ir brėžiate linijas stačiakampiais keliais. Aritmetika priklauso jums, bet ji yra paprasta, ir kartą parašyta apibendrinama bet kokiam jums reikalingam tinkleliui

procedure DrawTable(Pdf: TPdf; Left, Top: Double);
const
  ColX: array[0..2] of Double = (0, 110, 210);  // column offsets
  RowH = 20;
var
  Y: Double;
  Row: Integer;
begin
  // Header row
  Pdf.AddText('Item', 'Arial', 10, Left + ColX[0], Top);
  Pdf.AddText('Qty', 'Arial', 10, Left + ColX[1], Top);
  Pdf.AddText('Price', 'Arial', 10, Left + ColX[2], Top);

  // Rule under the header
  Pdf.CreatePath(Left, Top - 5, 260, 0.5, fmNone, clBlack, $FF);
  Pdf.AddPath;

  // Data rows, stepping Y downward each iteration
  Y := Top;
  for Row := 1 to 3 do
  begin
    Y := Y - RowH;
    Pdf.AddText('Item ' + IntToStr(Row), 'Arial', 9, Left + ColX[0], Y);
    Pdf.AddText(IntToStr(Row * 2), 'Arial', 9, Left + ColX[1], Y);
    Pdf.AddText('$' + IntToStr(Row * 10) + '.00', 'Arial', 9, Left + ColX[2], Y);
  end;
end;

Atkreipkite dėmesį, kad kiekvieną kartą Y žengia žemyn per eilutės aukštį, vėlgi todėl, kad kryptis į viršų yra teigiama. Čia taip pat išryškėja teksto matavimo nebuvimas: niekas netrukdo ilgam elemento pavadinimui peržengti į kitą stulpelį, nes biblioteka nežino, kokio pločio atvaizduota jūsų eilutė. Fiksuoto formato išvestims, kur jūs valdote duomenis, jūs dosniai nustatote stulpelių dydžius ir judate toliau. Tikrai kintamam turiniui, jūs arba apribojate įvestis, arba patys išmatuojate glifų pločius prieš juos talpindami, o tai yra tas momentas, kai specializuota kompozicijos biblioteka pradeda atsipirkti

Paveikslėliai ir keli puslapiai

Rastrinis turinys ateina per paveikslėlių pagalbininkus. AddPicture priima įkeltą TPicture ir patalpina jį taške, su pasirinktiniu pločiu ir aukščiu mastelio keitimui; AddImage priima failo kelią arba TBitmap tiesiogiai, o AddJpegImage transliuoja JPEG baitus be apsilankymo per bitmap'ą. Kaip ir visur kitur, išdėstymo koordinatės yra apatinis kairysis paveikslėlio kampas vartotojo erdvėje, o plotis ir aukštis yra puslapio dydis taškais, o ne šaltinio pikselių matmenys

procedure CreateMultiPageReport(const FileName: string; PageCount: Integer);
var
  Pdf: TPdf;
  P: Integer;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.CreateDocument;
    for P := 1 to PageCount do
    begin
      Pdf.AddPage(P, 595, 842);     // append; the new page becomes current
      Pdf.AddText('Page ' + IntToStr(P) + ' of ' + IntToStr(PageCount),
        'Arial', 10, 50, 30);       // footer near the bottom edge
      // ... draw this page's body here ...
    end;
    Pdf.SaveAs(FileName);
  finally
    Pdf.Active := False;
    Pdf.Free;
  end;
end;

Kelių puslapių dokumentas yra vieno puslapio šablonas cikle. Kiekvienas AddPage prideda puslapį ir padaro jį dabartiniu, todėl toliau piešiami turinys (body) ir poraštė (footer) atsiduria ką tik pridėtame puslapyje. Šiame cikle jums nereikia iš naujo priskirti PageNumber, nes puslapio pridėjimas jau perkėlė ten žymeklį; PageNumber jums reikia tik tada, kai grįžtate prie puslapio ne kūrimo tvarka. Iškvieskite SaveAs vieną kartą pabaigoje, užpildę paskutinį puslapį. Jei jums reikia archyvinio profilio, o ne paprasto failo, tas pats dokumento objektas pateikia SaveAsPdfA ir kitus atitikties variantus, todėl išvesties standarto pasirinkimas yra kitoks išsaugojimo iškvietimas, o ne kitas kūrimo kelias

Kur tai tinka

Sąžiningas rėminimas (framing) yra tai, kad PDFium Component kūrimo API yra ištikimas, plonas sluoksnis virš PDFium puslapių objektų modelio: tikras dokumento kūrimas, tikri įterptieji šriftai, tikras vektorinis ir rastrinis turinys, serijuojamas į standartus atitinkantį failą. Tai nėra, ir nepretenduoja būti, perpintų dokumentų (reflowing document) varikliu. Skiriamoji linija yra teksto išdėstymas. Jei jūsų išvestis yra šabloninė, sąskaitos faktūros, sertifikatai, etiketės, prietaisų skydeliai, atvaizduojami fiksuotame tinklelyje, absoliučių koordinačių modelis yra tiesioginis bei greitas, o kodas išlieka skaitomas. Jei jūsų išvestis yra ilgos formos proza, kuri turi pati persikelti ir skaidytis puslapiais, jums teks perkurti išdėstymo variklį ant šių iškvietimų viršaus, o tam tai yra netinkamas įrankis. Žinojimas, kurioje šios linijos pusėje esate, yra didžioji sprendimo dalis

Čia aprašyti kūrimo metodai yra PDFium Component, skirto Delphi, dalis, kuris suporuoja šį kūrimo kelią su atvaizdavimo ir teksto išgavimo funkcijomis, kuriomis PDFium yra labiau žinomas