Poziv koji stavlja tekst na PDF stranicu je jednostavan. Dajete AddText-u string, font, veličinu i poziciju, i glifovi se pojavljuju. Ono što ne radi je govoriti vam kolika će ta niska biti jednom nacrtana, i ne lomi dugu nisku po nekoliko redova. Jedan poziv slika jedan niz teksta na jednoj poziciji. Ako je niz širi od kolone u koju ste ga namjeravali smjestiti, jednostavno prelazi ivicu, i ništa u pozivu crtanja vas ne upozori. U trenutku kada želite paragraf umjesto jedne oznake, nedostajući dio je širina niske u odabranom fontu i veličini, izmjerena prije nego što je potvrdite na stranicu
Ovo je klasičan problem rasporeda. Da biste prelomili paragraf u kolonu morate znati, riječ po riječ, koliko horizontalnog prostora će svaki kandidat reda uzeti, i morate to znati unaprijed od crtanja bilo čega. Prelom reda je petlja mjerenja omotana oko poziva crtanja, i binding koji samo crta vam daje drugu polovinu. Podrška mjerenja teksta u PDFium komponenti zatvara taj jaz s dvije funkcije, MeasureText i MeasureTextWidth, koje prijavljuju prikazani raspon niske bez stavljanja ikakve oznake na bilo koju stranicu
Zašto je mjerenje class helper, a ne nova metoda na TPdf
Podrška mjerenja stiže kao Delphi class helper za TPdf, živući u vlastitoj jedinici, umjesto kao nove metode zalemljene u klasu TPdf. Class helper je jezična značajka koja vam dozvoljava da prikačite metode na postojeći tip izvana njegove deklaracije. Jednom kada je jedinica u opsegu, nove metode se pozivaju tačno kao da pripadaju klasi, pa se helper metoda čita kao Pdf.MeasureTextWidth(...) bez zasebnog objekta za konstruisanje ili prolaženje okolo
Razlog za slojevanje na ovaj način je odvajanje. Osnovni tip TPdf ostaje kakav jest, bez dodanog polja i bez dodirnute postojeće potpise, pa projekat koji nikada ne treba raspored nikad ne nosi kod mjerenja. Projekat koji to treba dodaje jednu jedinicu u uses klauzulu i metode se pale. Sposobnost postaje opt-in na granularnosti jedne jedinice, što je najčišći način za proširenje tipa koji ne posjedujete ili ne želite poremetiti
uses
PDFium, FPdfView, FPdfEdit,
FPdfMeasure; // the helper unit; brings MeasureText into scope on TPdf
// With the unit in scope the methods read as members of TPdf:
var
W, H: Double;
begin
Pdf.MeasureText('Subtotal', 'Helvetica', 11, W, H);
// W and H are now the rendered width and height in PDF user units
end;
Mjerenje bez dodirivanja stranice
Mjerenje mora biti slobodno od nuspojava. Mora prijaviti širinu bez ostavljanja ičega za sobom, jer ga pozivate mnogo puta dok odlučujete raspored i stranica mora izgledati tačno kao da nikad niste mjerili. Tehnika koja ovo čini mogućim je izgraditi tekstualni objekt, upitati ga za veličinu, i baciti ga prije nego što ikad bude priključen na stranicu
Slijed je četiri PDFium poziva. FPDFPageObj_NewTextObj kreira tekstualni objekt protiv dokumenta, dado ime fonta i veličinu. FPDFText_SetText postavlja nisku koju taj objekt nosi. FPDFPageObj_GetBounds čita natrag granični okvir objekta. FPDFPageObj_Destroy oslobađa objekt. Kritično, ništa u tom slijedu ne poziva API za umetanje stranice. Objekt je kreiran, upitan i uništen u izolaciji, pa je dokument neizmijenjen kada se funkcija vrati. To je jednokratna sonda čiji je jedini izlaz četiri broja njenog graničnog okvira
Ovo je robustan način za to jer PDFium ne izlaže zgodnu naprednu širinu po glifu koju biste mogli sami sabrati. Metrike glifova ovise o programu fonta, enkodiranju, i tome kako PDFium učitava lice, i ne postoji javni poziv koji vam daje napredak svakog znaka u niski. Granični okvir pravog tekstualnog objekta, s druge strane, izračunava isti mehanizam koji bi postavio glifove za crtanje, pa odražava stvarni prikazani raspon umjesto aproksimacije. Izgradnja jednog jednokratnog objekta i čitanje njegovih granica je najpouzdanije mjerenje koje biblioteka može dati
// The shape of MeasureText, expressed against the verified PDFium calls.
// A text object is built, measured, and destroyed; no page is involved.
procedure TPdfMeasureHelper.MeasureText(const Text, Font: WString;
FontSize: Single; out Width, Height: Double);
var
TextObject: FPDF_PAGEOBJECT;
L, B, R, T: Single;
begin
Width := 0;
Height := 0;
if Self.Document = nil then
Exit;
TextObject := FPDFPageObj_NewTextObj(Self.Document,
FPDF_BYTESTRING(AnsiString(Font)), FontSize);
if TextObject = nil then
Exit;
try
if FPDFText_SetText(TextObject, FPDF_WIDESTRING(WideString(Text))) = 0 then
Exit;
if FPDFPageObj_GetBounds(TextObject, L, B, R, T) <> 0 then
begin
Width := R - L;
Height := T - B;
end;
finally
FPDFPageObj_Destroy(TextObject); // probe discarded, page untouched
end;
end;
Koordinate i jedinice rezultata
Granični okvir se vraća kao četiri ivice, lijeva, donja, desna i gornja, i dvije dimenzije proizlaze oduzimanjem. Širina je desno minus lijevo a visina je gore minus dolje. Oboje su izraženi u PDF korisničkim jedinicama, gdje je jedna jedinica jednakaset i dvanaesta dio inča, isti koordinatni prostor u kojem postavljate tekst na stranici. Nema skrivene uređajne jedinice i nema piksela umiješanog u ovoj fazi. Širina od 36 znači pola inča stranice, bez obzira na konačnu rezoluciju renderovanja
Vertikalna osa teče na način na koji PDF to definira, s Y koji se povećava prema gore, što je razlog zašto je visina gore minus dolje, a ne obrnuto. Taj detalj je bitan kada pomičete kursor dolje niz kolonu. Mjerite visinu reda, zatim je oduzimate od trenutne bazne linije da biste pronašli sljedeću, jer kretanje dolje po stranici znači kretanje prema manjem Y. Ako je vaše odredište ekran umjesto papira, konvertirate korisničke jedinice u piksele uređaja s rezolucijom prikaza: vrijednost u korisničkim jedinicama pomnožena s DPI i podijeljena s 72 daje piksele, pa širina kolone koju postavite u tačkama može biti uspoređena s izmjerenim nizom prije nego što odlučite gdje ide prelom
Šta se dešava na degeneriranom unosu
Funkcije su napisane da tiho ne uspiju. Ako nema otvorenog dokumenta, ili ako tekstualni objekt ne može biti kreiran, rezultat je nulti raspon umjesto izbačene iznimke. Širina i visina su inicijalizirane na nulu na vrhu i prepisuju se samo jednom kada je granični okvir uspješno pročitan natrag. Prazan string, dokument koji nedostaje, font koji biblioteka ne može razriješiti u objekt, svako od ovih vraća nulu umjesto bacanja
Taj izbor drži petlju mjerenja jednostavnom, jer petlja koja se pokreće nad hiljadama riječi nije mjesto za rukovanje iznimkama na svakoj iteraciji. Cijena je da pozivalac nosi provjeru. Nulta širina je sentinel, ne činjenica o tekstu, pa kod koji dijeli izmjerenom širinom ili pretpostavlja pozitivnu vrijednost mora štititi od nule prije nego što joj povjeruje. Tretirajte nulu kao "nije mogao izmjeriti" i ugovor je jasan; ignorirajte ga i degenerirani unos tiho postaje raspored s kolonom preklapajućih glifova
Pohlepni prelom reda izgrađen na mjerenju
S funkcijom širine pri ruci, prelom reda je kratka pohlepna petlja. Dijelite paragraf na riječi, zadržavate trenutni red, i za svaku riječ mjerite kakav bi red bio da ste dodali tu riječ. Dok probni red još uvijek staje u širinu kolone nastavite dodavati; kada bi prelio, ispirate trenutni red s AddText-om i počinjete novi s riječju koja nije stala. Akumulacija se radi potpuno s MeasureTextWidth-om, i jedina stvar koja ikad dosegne stranicu je red koji ste već potvrdili da staje
procedure WrapParagraph(Pdf: TPdf; const Para, Font: WString;
FontSize: Single; X, TopY, ColumnWidth, LineHeight: Double);
var
Words: TArray<WideString>;
Line, Trial: WideString;
I: Integer;
Y: Double;
begin
Words := WideString(Para).Split([' ']);
Line := '';
Y := TopY;
for I := 0 to High(Words) do
begin
if Line = '' then
Trial := Words[I]
else
Trial := Line + ' ' + Words[I];
// Measure the candidate line before drawing anything.
if (Line <> '') and (Pdf.MeasureTextWidth(Trial, Font, FontSize) > ColumnWidth) then
begin
Pdf.AddText(X, Y, Font, FontSize, Line); // flush the line that fit
Y := Y - LineHeight; // Y decreases going down
Line := Words[I]; // overflowing word starts next line
end
else
Line := Trial;
end;
if Line <> '' then
Pdf.AddText(X, Y, Font, FontSize, Line); // flush the final line
end;
Petlja mjeri probni red umjesto mjerenja svake riječi i zbrajanja, jer širina reda nije zbroj širina njegovih riječi. Razmaci između riječi doprinose, i izmjereni niz to direktno hvata. Pohlepno pravilo, stavi što više riječi kolona dozvoljava i prelomi na posljednjoj koja stane, je isto pravilo koje popunjava jaz između sirovog AddText-a i pravog paragrafa. Poziv crtanja nikad nije bio teški dio. Mjerenje koje mu mora prethoditi jeste, i to je tačno ono što helper pruža
Gdje ovo staje
Mjerenje je sloj između generisanja sadržaja i renderovanja, pa se prirodno sparuje s ostatkom workflow-a za dokumente ispočetka. Ako sastavljate stranice i postavljate tekst na prvom mjestu, osnova je u kreiranju PDF dokumenata ispočetka s PDFium komponentom u Delphi-ju, gdje su AddText i postavljanje stranica obrađeni u potpunosti. Kada je font koji mjerite jednako bitan kao i niska, jer metrike ovise o licu, analiza svojstava PDF fonta s PDFium komponentom u Delphi-ju pokazuje kako biblioteka prijavljuje informacije o fontu koje pokreću te granične okvire. Oboje grade na istom bindingu, PDFium Component-u za Delphi i Lazarus, gdje se helper za mjerenje isporučuje uz dokument, stranicu i API-je teksta opisane kroz ovaj blog