Klic, ki postavi besedilo na PDF stran, je preprost. Metodi AddText podate niz, pisavo, velikost in položaj, in glifi se pojavijo. Kar pa ne stori, je, da bi vam povedala, kako širok bo ta niz, ko bo narisan, in ne prelomi dolgega niza v več vrstic. En sam klic nariše en potek besedila na eni poziciji. Če je potek širši od stolpca, v katerega naj bi se prilegal, preprosto steče čez rob, in nič v risalnem klicu vas na to ne opozori. V trenutku, ko želite odstavek namesto ene same oznake, je manjkajoči del širina niza v izbrani pisavi in velikosti, izmerjena preden ga postavite na stran
To je klasičen problem postavitve. Da bi prelomili odstavek v stolpec, morate vedeti, besedo za besedo, koliko vodoravnega prostora bo zavzela vsaka kandidatna vrstica, in to morate vedeti, preden karkoli narišete. Prelom besed je merilna zanka, ovita okoli risalnega klica, vezava, ki samo riše, pa vam da le drugo polovico. Podpora za merjenje besedila v komponenti PDFium to vrzel zapre z dvema funkcijama, MeasureText in MeasureTextWidth, ki poročata o izrisanem obsegu niza, ne da bi pri tem na katero koli stran narisali karkoli
Zakaj je merjenje razredni pomočnik in ne nova metoda na TPdf
Podpora za merjenje pride kot Delphi razredni pomočnik (class helper) za TPdf, ki živi v svoji lastni enoti, namesto kot nove metode, dodane v razred TPdf. Razredni pomočnik je jezikovna značilnost, ki vam omogoča, da metode pripnete na obstoječ tip od zunaj njegove deklaracije. Ko je enota v dosegu, se nove metode kličejo natanko tako, kot da bi bile del razreda, zato se metoda pomočnika bere kot Pdf.MeasureTextWidth(...) brez ločenega objekta, ki bi ga bilo treba zgraditi ali prenašati naokoli
Razlog za tako plastenje je ločevanje. Osnovni tip TPdf ostane tak, kot je, brez dodanega polja in brez dotika obstoječega podpisa, tako da projekt, ki nikoli ne potrebuje postavitve, nikoli ne nosi kode za merjenje. Projekt, ki jo potrebuje, doda eno enoto v stavek uses in metode zaživijo. Zmožnost postane prostovoljna na ravni ene same enote, kar je najčistejši način za razširitev tipa, ki ga ne posedujete ali ga nočete motiti
uses
PDFium, FPdfView, FPdfEdit,
FPdfMeasure; // enota pomočnika; prinese MeasureText v doseg na TPdf
// Z enoto v dosegu se metode berejo kot člani TPdf:
var
W, H: Double;
begin
Pdf.MeasureText('Subtotal', 'Helvetica', 11, W, H);
// W in H sta zdaj izrisana širina in višina v PDF uporabniških enotah
end;
Merjenje brez dotikanja strani
Merjenje mora biti brez stranskih učinkov. Poročati mora o širini, ne da bi kaj pustilo za sabo, saj ga kličete mnogokrat med določanjem postavitve, stran pa mora na koncu izgledati natanko tako, kot bi izgledala, če sploh ne bi nič merili. Tehnika, ki to omogoča, je, da zgradite tekstovni objekt, ga vprašate po velikosti in ga zavržete, še preden je sploh pritrjen na stran
Zaporedje je štiri klici PDFium. FPDFPageObj_NewTextObj ustvari tekstovni objekt na dokumentu, glede na dano ime pisave in velikost. FPDFText_SetText določi niz, ki ga ta objekt nosi. FPDFPageObj_GetBounds prebere nazaj mejno okence objekta. FPDFPageObj_Destroy objekt sprosti. Ključno je, da nič v tem zaporedju ne pokliče API-ja za vstavljanje na stran. Objekt je ustvarjen, poizvedovan in uničen v izolaciji, tako da je dokument nespremenjen, ko funkcija vrne rezultat. Gre za sondo za enkratno uporabo, katere edini izhod so štiri številke njenega mejnega okenca
To je zanesljiv način, ker PDFium ne izpostavi priročne širine predujma za posamezen glif, ki bi jo lahko sešteli sami. Metrike glifov so odvisne od programa pisave, kodiranja in od tega, kako PDFium naloži obraz pisave, javnega klica, ki bi vam vrnil predujem vsakega znaka v nizu, pa ni. Mejno okence resničnega tekstovnega objekta pa na drugi strani izračuna isti mehanizem, ki bi glife razporedil za risanje, zato odraža dejanski izrisani obseg in ne približka. Gradnja enega enkratnega objekta in branje njegovih mej je najzanesljivejša meritev, ki jo knjižnica lahko da
// Oblika MeasureText, izražena glede na preverjene klice PDFium.
// Tekstovni objekt se zgradi, izmeri in uniči; nobena stran ni vpletena.
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); // sonda zavržena, stran nedotaknjena
end;
end;
Koordinate in enote rezultata
Mejno okence se vrne kot štirje robovi: levi, spodnji, desni in zgornji, obe dimenziji pa dobimo z odštevanjem. Širina je desna minus leva, višina pa zgornja minus spodnja. Obe sta izraženi v PDF uporabniških enotah, kjer je ena enota ena dvainsedemdesetinka palca, isti koordinatni prostor, v katerem besedilo postavljate na stran. V tej fazi ni nobene skrite enote naprave in nobenega piksla. Širina 36 pomeni pol palca strani, ne glede na končno ločljivost izrisa
Navpična os poteka tako, kot jo določa PDF, z Y, ki narašča navzgor, zato je višina zgornja minus spodnja in ne obratno. Ta podrobnost je pomembna, ko kazalko pomikate navzdol po stolpcu. Izmerite višino vrstice, nato jo odštejete od trenutne osnovne črte, da najdete naslednjo, ker pomik navzdol po strani pomeni pomik proti manjšemu Y. Če je vaš cilj zaslon namesto papirja, uporabniške enote pretvorite v slikovne pike naprave z ločljivostjo zaslona: vrednost v uporabniških enotah, pomnožena z DPI in deljena s 72, da slikovne pike, tako da lahko širino stolpca, določeno v točkah, primerjate z izmerjenim potekom, preden se odločite, kje bo prelom
Kaj se zgodi pri izrojenem vhodu
Funkciji sta napisani tako, da tiho odpovesta. Če ni odprtega dokumenta ali če tekstovnega objekta ni mogoče ustvariti, je rezultat ničelni obseg namesto sprožene izjeme. Širina in višina sta na začetku inicializirani na nič in prepisani šele, ko je mejno okence uspešno prebrano nazaj. Prazen niz, manjkajoč dokument, pisava, ki je knjižnica ne more razrešiti v objekt — vsak od teh primerov vrne nič namesto da bi sprožil izjemo
Ta izbira ohranja merilno zanko preprosto, ker zanka, ki teče čez tisoče besed, ni mesto za obravnavo izjem pri vsaki iteraciji. Cena je, da preverjanje nosi klicatelj. Ničelna širina je varovalna vrednost, ne dejstvo o besedilu, zato mora koda, ki deli z izmerjeno širino ali predpostavlja pozitivno vrednost, pred zaupanjem preveriti, ali je vrednost nič. Če ničlo obravnavate kot "ni bilo mogoče izmeriti", je pogodba jasna; če to prezrete, izrojen vhod tiho postane postavitev s stolpcem prekrivajočih se glifov
Požrešen prelom besed, zgrajen na merjenju
S funkcijo za širino v rokah je prelom besed kratka požrešna zanka. Odstavek razdelite na besede, hranite trenutno vrstico in za vsako besedo izmerite, kakšna bi bila vrstica, če bi to besedo dodali. Dokler poskusna vrstica še ustreza širini stolpca, jo naprej dodajate; ko bi prišlo do prekoračitve, trenutno vrstico izpišete s AddText in začnete novo z besedo, ki ni ustrezala. Seštevanje poteka izključno z MeasureTextWidth, na stran pa kadar koli pride le vrstica, za katero ste že potrdili, da ustreza
procedure WrapParagraph(Pdf: TPdf; const Para, Font: WString;
FontSize: Single; X, TopY, ColumnWidth, LineHeight: Double);
var
Words: TArray<string>;
Line, Trial: WideString;
I: Integer;
Y: Double;
begin
Words := string(Para).Split([' ']);
Line := '';
Y := TopY;
for I := 0 to High(Words) do
begin
if Line = '' then
Trial := Words[I]
else
Trial := Line + ' ' + Words[I];
// Izmerite kandidatno vrstico, preden karkoli narišete.
if (Line <> '') and (Pdf.MeasureTextWidth(Trial, Font, FontSize) > ColumnWidth) then
begin
Pdf.AddText(Line, Font, FontSize, X, Y); // izpiši vrstico, ki je ustrezala
Y := Y - LineHeight; // Y se zmanjšuje navzdol
Line := Words[I]; // beseda, ki ni ustrezala, začne naslednjo vrstico
end
else
Line := Trial;
end;
if Line <> '' then
Pdf.AddText(Line, Font, FontSize, X, Y); // izpiši zadnjo vrstico
end;
Zanka meri poskusno vrstico namesto da bi merila vsako besedo posebej in jih seštevala, ker širina vrstice ni vsota širin njenih besed. Presledki med besedami prispevajo svoje, izmerjeni potek pa to zajame neposredno. Požrešno pravilo, spravite vanjo toliko besed, kot jih stolpec dovoljuje, in prelomite pri zadnji, ki še ustreza, je isto pravilo, ki zapolni vrzel med golim AddText in pravim odstavkom. Risalni klic nikoli ni bil težji del. Merjenje, ki mora priti pred njim, pa je, in prav to zagotavlja pomočnik
Kam se to umešča
Merjenje je plast med ustvarjanjem vsebine in njenim izrisom, zato se naravno poveže z ostalim potekom dela pri gradnji dokumenta od začetka. Če sestavljate strani in postavljate besedilo na novo, so temelji v članku ustvarjanje PDF dokumentov od začetka s komponento PDFium v Delphiju, kjer sta AddText in nastavitev strani obravnavana v celoti. Kadar je pisava, ki jo merite, prav tako pomembna kot niz sam, ker so metrike odvisne od obraza pisave, analiziranje lastnosti PDF pisav s komponento PDFium v Delphiju pokaže, kako knjižnica poroča o podatkih o pisavi, ki poganjajo ta mejna okenca. Oba gradita na isti vezavi, PDFium Component za Delphi in Lazarus, kjer je pomočnik za merjenje dobavljen skupaj z API-ji za dokument, stran in besedilo, opisanimi po tem blogu