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ų

Keturių žingsnių PDF kūrimo eigos PDFium Component for Delphi diagrama: nuo CreateDocument per AddPage ir turinio iškvietimus iki SaveAs
CreateDocument pradeda tuščiąjį atminties dokumentą, kiekvienas AddPage tampa esamuoju puslapiu, o SaveAs serializuoja sudėtuosius PDF objektus į diską
uses
  Vcl.Graphics,   // clBlack ir TColor
  PDFium;         // čia gyvena TPdf

procedure CreateBlankPdf(const FileName: string);
var
  Pdf: TPdf;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.CreateDocument;                 // tuščias dokumentas atmintyje
    Pdf.AddPage(0, 595, 842);           // A4 stačias, taškais
    Pdf.AddText('First page', 'Arial', 18, 50, 780);
    Pdf.SaveAs(FileName);               // serializuoti į 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

PDFium Component diagrama, kontrastuojanti PDF vartotojo erdvę, kurios pradžia sėdi apačioje kairėje, Y auga aukštyn, su ekrano koordinatėmis, kurių Y auga žemyn nuo viršaus-kairės
Antraštė netoli A4 puslapio viršaus reikalauja Y apie 780 PDF naudotojo erdvėje, kol ekrano įpročiai užrašytų Y 60 ir nuleistų tekstą netoli apačios

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
  // Antraštė juoda, numatytasis nepermatomumas, be pasukimo
  Pdf.AddText(Title, 'Arial', 20, 50, 780);
  // Šviesesnė autoriaus eilutė 24 taškais žemiau
  Pdf.AddText('By ' + Author, 'Arial', 11, 50, 756, clGray);
  // Silpnas įstrižas juodraščio antspaudas per puslapį
  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

Vektorinio kelio gyvavimo ciklo PDFium Component diagrama, kur CreatePath nustato pradžios tašką ir stilių, LineTo ir BezierTo brėžia kontūrą, o AddPath įrašo piešinį
CreatePath užfiksuoja pradinįjį tašką ir kiekvieną stilių iš anksto, bet niekas nepasirodo puslapyje, kol AddPath įsipareigoja užbaigtąjį kelią
procedure DrawDivider(Pdf: TPdf; X, Y, Width: Single);
begin
  // Plona horizontali linija. Stačiakampio perkrova tiesiogiai nustato langelį:
  // X, Y, Width, Height, tada užpildymo režimas ir spalvos.
  Pdf.CreatePath(X, Y, Width, 0.5, fmNone, clBlack, $FF,
    True, clBlack, $FF, 1.0);
  Pdf.AddPath;
end;

procedure DrawTriangle(Pdf: TPdf);
begin
  // Taško perkrova: pradėti nuo pirmos viršūnės, vesti liniją per likusias, uždaryti.
  Pdf.CreatePath(200, 300, fmWinding, clBlue, $80, True, clNavy, $FF, 2.0);
  Pdf.LineTo(300, 300);
  Pdf.LineTo(250, 400);
  Pdf.ClosePath;
  Pdf.AddPath;          // niekas nenupiešiama, kol tai nepaleidžiama
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);  // stulpelių poslinkiai
  RowH = 20;
var
  Y: Double;
  Row: Integer;
begin
  // Antraštės eilutė
  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);

  // Linija po antrašte
  Pdf.CreatePath(Left, Top - 5, 260, 0.5, fmNone, clBlack, $FF);
  Pdf.AddPath;

  // Duomenų eilutės, kiekvienoje iteracijoje Y žengiantis žemyn
  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);     // pridėti; naujas puslapis tampa dabartiniu
      Pdf.AddText('Page ' + IntToStr(P) + ' of ' + IntToStr(PageCount),
        'Arial', 10, 50, 30);       // poraštė netoli apatinio krašto
      // ... čia piešti šio puslapio turinį ...
    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