Poziv kojim postavljate tekst na PDF stranicu je posve izravan. Proslijedite metodi AddText neki string, font, veličinu te poziciju, a odgovarajući glifovi će se pojaviti. Ono što on ne radi jest to da vam ne govori koliko će taj isti string biti širok jednom kada se nacrta te neće automatski prelomiti dugi string u više redaka. Pojedinačni poziv iscrtava samo jedan dio teksta na jednoj jedinoj poziciji. Ukoliko je taj iscrtani dio širi od stupca u koji ste ga mislili uklopiti, on naprosto izlazi preko ruba, a sam crtaći poziv vas ni na koji način na to ne upozorava. U onom trenutku kada umjesto obične jednokratne oznake zatrebate čitav odlomak, nedostajući dio slagalice je upravo širina tog stringa odabranim fontom i navedenom veličinom, koja bi trebala biti izmjerena prije nego što išta od toga trajno postavite na stranicu
To je taj klasični problem prijeloma. Da biste zamotali i prelomili odlomak unutar stupca morate znati, riječ po riječ, točno onoliko prostora koliko će svaki budući redak zauzeti na horizontalnoj skali i točno to jednostavno morate doznati i prije ikakvog iscrtavanja. Prelamanje riječi je dakle mjerna petlja omotana oko onog poziva za crtanje, stoga vam vezanje koje isključivo crta osigurava upravo samo tu drugu polovinu problema. Podrška za mjerenje teksta unutar PDFium komponente premošćuje taj isti jaz uz pomoć dviju funkcija, MeasureText te MeasureTextWidth, koje obje vrlo izvještavaju o obujmu takvog stringa prije njegova renderiranja ne povlačeći tako za sobom ni najmanju oznaku igdje po toj stranici
Zašto je mjerenje razredni pomoćnik (class helper), a ne nova metoda na TPdf
Podrška za mjerenje dolazi kao Delphijev pomoćnik klase za TPdf, živeći u svom vlastitom unitu, umjesto kao nove metode nasilu dodane u samu klasu TPdf. Pomoćnik klase je jezična značajka koja vam omogućuje pridruživanje metoda postojećem tipu izvan njegove deklaracije. Kada je taj unit u dosegu, nove se metode pozivaju točno kao da pripadaju samoj klasi, pa se tako pomoćna metoda čita kao Pdf.MeasureTextWidth(...), bez ikakvog zasebnog objekta kojeg biste morali konstruirati ili prosljeđivati uokolo
Razlog da se ovo strukturira na ovakav način jest razdvajanje. Temeljni tip TPdf ostaje onakav kakav jest, pri čemu se nijedno polje ne dodaje, a niti se dotiče bilo koji postojeći potpis. Prema tome, onaj projekt kojem prijelom nikada ne zatreba, u sebi nikada niti ne nosi s time povezani mjerni kod. S druge pak strane, projekt kojem to zatreba jednostavno doda jedan unit u svoju uses klauzulu i te metode jednostavno ožive. Sposobnost postaje opcionalna na granularnosti jednog unita, što je ujedno i najčišći način za proširivanje tipa kojeg ili ne posjedujete ili kojeg naprosto ne želite nepotrebno uznemiravati
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 doticanja stranice
Mjerenje mora biti lišeno bilo kakvih nuspojava. Mora izvještavati o širini, a da za sobom ne ostavlja apsolutno nikakav trag, jer ga pozivate mnogo puta dok tek odlučujete o prijelomu, stoga stranica u konačnici mora izgledati točno onako kao što bi to izgledala da nikakvo mjerenje uopće niste ni vršili. Tehnika koja ovo omogućava svodi se na to da se izradi tekstualni objekt, zatraži od njega informacija o njegovoj veličini i naprosto ga se odbaci i prije nego on uopće postane pričvršćen na ikakvu stranicu
Slijed se sastoji od četiri PDFium poziva. FPDFPageObj_NewTextObj stvara tekstualni objekt naspram dokumentu, temeljem imena i veličine fonta. FPDFText_SetText postavlja string kojeg taj objekt nosi. FPDFPageObj_GetBounds očitava granični okvir tog objekta. FPDFPageObj_Destroy oslobađa objekt. Ključno je to da ništa u tom slijedu ne poziva API za umetanje na stranicu. Objekt se stvara, ispituje i uništava u izolaciji, tako da po povratku funkcije dokument ostaje posve nepromijenjen. Riječ je o jednokratnoj sondi za bacanje čiji su jedini izlaz upravo ta četiri broja njezinog graničnog okvira
Ovo je robustan način, budući da PDFium nigdje ne izlaže pogodnu predujmovnu širinu za svaki pojedini glif koju biste mogli sami zbrojiti. Metrika glifova ovisi o programu fonta, o samom kodiranju, kao i o tome na koji način PDFium učitava njegovo lice, a pritom ne postoji nikakav javni poziv koji bi vam dostavio iznos predujma svakog znaka unutar stringa. S druge pak strane, granični okvir stvarnog tekstualnog objekta računa isti onaj mehanizam koji bi raspoređivao te iste glifove za crtanje, stoga on prilično odražava sami stvarni renderirani obujam puno više negoli tek nekakvu puku aproksimaciju. Izgradnja jednog ovakvog lako odbacivog objekta te puko očitavanje njegovih granica zapravo donosi i ujedno jest najpouzdanije mjerenje iz izlaganja kakvo vam navedena biblioteka općenito može pružiti
// 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 mjerne jedinice rezultata
Granični se okvir vraća kao četiri brida: lijevi, donji, desni te gornji, dok same dvije dimenzije proizlaze upravo iz njihovog oduzimanja. Širina je desno minus lijevo, a visina je gore minus dolje. Oboje su pritom izraženi u PDF korisničkim jedinicama, kod kojih jedna jedinica predstavlja jednu sedamdesetidruginu palca, što je točno onaj isti koordinatni prostor unutar kojeg smještate sami tekst na stranici. U ovoj se fazi ne upotrebljavaju apsolutno nikakve skrivene jedinice uređaja niti se dotičemo ikakvih piksela. Širina od 36 znači pola palca same stranice, ma kakva god pritom konačna razlučivost njezina renderiranja bila
Vertikalna os je usmjerena onako kako to i sam PDF definira, pri čemu Y raste uzlazno, što je ujedno razlog zašto se visina dobiva kao gore minus dolje, a ne obratno. Taj je detalj važan kada kursor pomičete niže niz stupac. Izmjerite visinu retka, a zatim je oduzmete od trenutačne osnovne linije kako biste utvrdili sljedeću, jer kretanje niz stranicu znači micanje prema manjem Y. Ako je vaše odredište ekran, a ne papir, korisničke ćete jedinice preračunati u piksele uređaja prema njegovoj razlučivosti prikaza: vrijednost u korisničkim jedinicama umnožena onom iz DPI i nakon toga podijeljena sa 72 na kraju daje piksele, te se stoga širina stupca koju postavite u točkama može suprotstaviti i usporediti s vašim odmjerenim nizom prije nego što točno odlučite gdje prelazi sam taj prijelom
Što se događa pri degeneriranom unosu
Funkcije su napisane tako da tiho ne uspijevaju. Ako nema otvorenog dokumenta ili se tekstualni objekt uopće ne može stvoriti, rezultat iznosi obujam nula umjesto izazvane iznimke. Širina i visina se odmah na samome početku inicijaliziraju na nulu i prebrisat će se tek onda i nakon što se uspješno odradi očitavanje samog graničnoga okvira. Prazan niz, nedostajući dokument ili font kojeg biblioteka ne može razriješiti u objekt, svaki od njih povratno umjesto bacanja pogreške isporučuje običnu nulu
Taj odabir ujedno i čuva samu mjernu petlju dosta jednostavnom, budući da takva petlja koja preskače preko na tisuće raznoraznih riječi obično zbilja nimalo nije ono pravo namjensko mjesto usred kojeg bi se provodilo bilo kakvo opterećujuće upravljanje iznimkama usred njene baš baš svake iteracije. Stvarni trošak po pitanju toga je činjenica po kojoj na koncu isključivo onaj koji izda poziv takav na sebi samom on tako preuzima pa nosi prigodnu takvu nužnu navedenu spomenutu puku i nužnu onakvu provjeru. Širina koja iskaže vrijednost od nula zapravo redovno ispada tek samo jedna svojevrsna sigurnosna čuvarska signalna takozvana zastavica iliti stražarska naoko spomenuta nulta straža i sentinel (sentinel), nipošto stvarna činjenica i podatak a glede i spram istoga teksta, iz kojeg se onako pak uočava da preostali kôd onaj takav a koji poslije redovno i bez greške na koncu potom on posve skroz dijeli tim mjerenjem pa očitava mjernom tim širinom te iz kojeg onako uslijed toga po njemu poslije pa potom u svemu isto potom usvaja te preuzima pretpostavke jedne posve obujmom isključivo pa obično strogo naime onakve puke čiste neke strogo onako tek pozitivne vrijednosti uslijed navedene u obzir uzete naprosto na koncu jedino te prvenstveno redovno jedino preostale i nadasve predočene puke te isto tako predočene same predočene straže mora uslijed nule biti posve spreman takvo nešto i to naime prvo unaprijed sasvim glatko zaštititi naspram navedene dotične iste tek navedene puke onakve iznimno nulte puke i naoko prazne nule sve isto prvenstveno ne bi li uopće tek tako prije sasvim onako prigodno naprosto tek njoj glatko naposljetku glatko sasvim unutra pa onda poslije onda sasvim unutra i glatko zapravo onda sasma povjerovao. Prigodno uzmite, naime, stoga da vaša pa dotična puka nula predstavlja "ne mogu naprosto prigodno izmjeriti" pa po tome takav naime ugovor posve je te naočigled sasma čist i jasan; ukoliko navedeni taj preskočite, vaš pak takav neki degenerirani degenerirani takav dotični jedan takozvani naoko zatečeni izlučeni onaj obični navedeni isti ovlašteni jedan takav neki ulaz glatko tiho samo naoko sasvim onako naočigled uredno samo te potiho i u potpunosti posve nečujno postaje te i pređe onomad tada pa točno od tad uslijed njega posve točno takvim nekim pukim pa običnim i naime sasvim obično tako iznesenim nekim takvim puko namještenim jednim nekakvim sasvim u pogledu tek najobičnijim naime prijelomom ukalupljenim redom sasma baš jednim ovlaštenim na koncu stupcem spomenutim onakvim istaknutim po izboru od kojega dotičnog po redu nekih onakvih prepunjenih samih te naoko usnulo odreda istih u pogledu preklapajućih samih raznih puke i navedene grupe takozvanih navedenih predočenih tih ovlaštenih glifova
Pohlepno prelamanje riječi sazdano povrh mjerenja
S funkcijom širine u ruci, prijelom riječi postaje vrlo kratka tzv. pohlepna petlja. Odjeljak dijelite na riječi, nadzirete trenutačni redak te za svaku pojedinu riječ obavljate mjerenje kako bi redak izgledao u slučaju kad biste na njega nadodali istu tu riječ. Sve dok vaša pokusna linija još uvijek slobodno stane pod gabarite širine stupca, nastavljate s dodavanjem; onoga trena pak kada prelazi zadanu granicu, otpremate s AddText trenutnu dotadašnju izgrađenu liniju i počinjete sastavljati sljedeću koristeći pri tome onu istu riječ koja se u tu prethodu prosto nije ni uklopila. Nakupljanje je potpuno izvršeno isključivo korištenjem poziva funkcije MeasureTextWidth, a na istu navedenu stranicu jedino na koncu dopire redak čije ste samo to ispravno poklapanje u potpunosti još već davno i unaprijed potvrdili
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];
// Measure the candidate line before drawing anything.
if (Line <> '') and (Pdf.MeasureTextWidth(Trial, Font, FontSize) > ColumnWidth) then
begin
Pdf.AddText(Line, Font, FontSize, X, Y); // 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(Line, Font, FontSize, X, Y); // flush the final line
end;
Petlja mjeri pokusnu liniju (trial line) umjesto da mjeri svaku riječ i zbraja, budući da širina retka nije puki zbroj širina njegovih riječi. Uključeni razmaci između riječi itekako doprinose, pa samo mjerenje to izravno hvata. Pohlepno pravilo za uskladbu onoliko riječi koliko dopusti stupac uz prekid na posljednjoj koja stane u potpunosti je identično pravilu koje popunjava prazninu između sirovog poziva AddText i pravog odlomka. Sam crtaći poziv zapravo nikada ni nije predstavljao onaj teži dio. Ono što mu mora obvezno prethoditi jest samo mjerenje, a to je upravo ono što navedeni pomagač isporučuje
Gdje se ovo uklapa
Mjerenje čini sloj koji razdvaja generiranje sadržaja od njegovog iscrtavanja, tako da se sasvim prirodno uklapa u ostatak onog tijeka rada gdje se dokument konstruira od samoga početka. Bilo da stranice najprije sastavljate na licu mjesta postavljajući tekst na njegovu poziciju, pravi temelj izložen je u članku kreiranje PDF dokumenata iz temelja pomoću PDFium komponente u Delphiju, u kojem su metoda AddText kao i postavljanje same stranice u potpunosti pokriveni. Onoga trenutka kada se font kojeg mjerite pokaže jednako bitnim kao i onaj sami string - obzirom da navedena metrika ovisi isključivo o njegovome licu - analiziranje svojstava PDF fonta u suradnji s PDFium komponentom u Delphiju precizno otkriva način pomoću kojeg ista ta biblioteka izvještava povratno s onim temeljnim informacijama o fontu zaduženima za pokretanje navedenih istih graničnih okvira. Oboje se nadograđuju povrh onog potpuno istog onog vezivanja, PDFium Component za jezike Delphi i Lazarus, tamo gdje se i onaj takozvani pomoćnik za mjerenja napokon slobodno isporučuje bok uz bok sa samim dokumentom, stranicom i spomenutim tekstovnim API-jima kakvi su u općenitosti opisani posvuda diljem cijeloga ovog navedenoga bloga